Merge pull request #185 from oasdiff/docs/link-diff-action #286
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: pr-comment | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| pr_comment_tolerates_oasdiff_fail_on: | |
| runs-on: ubuntu-latest | |
| name: Test pr-comment proceeds when oasdiff exits non-zero with output | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Stub oasdiff to simulate fail-on triggered | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/stub | |
| cat > /tmp/stub/oasdiff <<'STUB' | |
| #!/bin/sh | |
| # Simulate fail-on: emit valid JSON, then exit non-zero. | |
| echo '[]' | |
| exit 1 | |
| STUB | |
| chmod +x /tmp/stub/oasdiff | |
| # Minimum env the entrypoint reads | |
| mkdir -p /tmp/run | |
| export GITHUB_REF=refs/pull/123/merge | |
| export GITHUB_REPOSITORY=foo/bar | |
| export GITHUB_SHA=deadbeef | |
| export GITHUB_BASE_REF=main | |
| export GITHUB_STEP_SUMMARY=/tmp/run/step-summary | |
| cat > /tmp/run/event.json <<EVT | |
| {"pull_request":{"head":{"sha":"deadbeef"},"base":{"sha":"baadcafe"}}} | |
| EVT | |
| export GITHUB_EVENT_PATH=/tmp/run/event.json | |
| # Run with empty oasdiff_token: the script should reach the | |
| # "No oasdiff-token provided" branch and exit 0. If the fix | |
| # is missing, set -e would abort at the oasdiff invocation | |
| # (exit 1, no notice line). | |
| export PATH=/tmp/stub:$PATH | |
| set +e | |
| out=$(./pr-comment/entrypoint.sh \ | |
| 'specs/base.yaml' 'specs/revision.yaml' \ | |
| '' '' '' '' '' '' 2>&1) | |
| rc=$? | |
| set -e | |
| echo "--- entrypoint output ---" | |
| echo "$out" | |
| echo "--- exit code: $rc ---" | |
| if [ "$rc" -ne 0 ]; then | |
| echo "FAIL: expected exit 0 (script should reach the no-token skip), got $rc" >&2 | |
| exit 1 | |
| fi | |
| if ! echo "$out" | grep -q "No oasdiff-token provided"; then | |
| echo "FAIL: script aborted before reaching the no-token skip" >&2 | |
| exit 1 | |
| fi | |
| echo "PASS" | |
| pr_comment_aborts_on_oasdiff_real_failure: | |
| runs-on: ubuntu-latest | |
| name: Test pr-comment aborts when oasdiff exits non-zero with no output | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Stub oasdiff to simulate a real failure | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/stub | |
| cat > /tmp/stub/oasdiff <<'STUB' | |
| #!/bin/sh | |
| # Simulate a real failure: no stdout, non-zero exit. | |
| echo "oasdiff: spec not found" >&2 | |
| exit 2 | |
| STUB | |
| chmod +x /tmp/stub/oasdiff | |
| mkdir -p /tmp/run | |
| export GITHUB_REF=refs/pull/123/merge | |
| export GITHUB_REPOSITORY=foo/bar | |
| export GITHUB_SHA=deadbeef | |
| export GITHUB_BASE_REF=main | |
| export GITHUB_STEP_SUMMARY=/tmp/run/step-summary | |
| cat > /tmp/run/event.json <<EVT | |
| {"pull_request":{"head":{"sha":"deadbeef"},"base":{"sha":"baadcafe"}}} | |
| EVT | |
| export GITHUB_EVENT_PATH=/tmp/run/event.json | |
| export PATH=/tmp/stub:$PATH | |
| set +e | |
| out=$(./pr-comment/entrypoint.sh \ | |
| 'specs/base.yaml' 'specs/revision.yaml' \ | |
| '' '' '' '' '' '' 2>&1) | |
| rc=$? | |
| set -e | |
| echo "--- entrypoint output ---" | |
| echo "$out" | |
| echo "--- exit code: $rc ---" | |
| if [ "$rc" -ne 2 ]; then | |
| echo "FAIL: expected exit 2 (oasdiff exit propagated), got $rc" >&2 | |
| exit 1 | |
| fi | |
| if ! echo "$out" | grep -q "ERROR: oasdiff exited 2 with no output"; then | |
| echo "FAIL: expected the explicit no-output error message" >&2 | |
| exit 1 | |
| fi | |
| echo "PASS" | |
| pr_comment_handles_large_payload: | |
| runs-on: ubuntu-latest | |
| name: Test pr-comment jq payload survives a multi-MB changes array | |
| # Regression test for the jq argv ARG_MAX limit. Real-world specs can | |
| # produce changelogs in the multi-MB range; the original implementation | |
| # passed `$changes` via `jq --argjson changes "$changes"` which put the | |
| # entire JSON string on jq's command line and exceeded ARG_MAX (typical | |
| # Linux ceiling is 2 MB), surfacing as `jq: Argument list too long` and | |
| # aborting the action right before the service POST. The fix pipes the | |
| # changes payload via stdin instead. This job stubs oasdiff to emit a | |
| # ~4 MB changelog and asserts the entrypoint reaches the no-token skip | |
| # without aborting, which proves the jq invocation processed the | |
| # payload successfully. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Stub oasdiff to emit a multi-MB changelog | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/stub | |
| # Pre-build a ~2 MB filler string to embed inside two synthetic | |
| # change entries (~4 MB total payload, well above the 2 MB Linux | |
| # ARG_MAX ceiling). dd reads a bounded number of zero bytes and | |
| # exits 0 cleanly, then tr converts to 'a's. We avoid `yes ... | | |
| # head -c N` because `head` closes the pipe and `yes` exits with | |
| # SIGPIPE, which fails under `set -o pipefail`. | |
| dd if=/dev/zero bs=1024 count=2000 status=none | tr '\0' 'a' > /tmp/filler | |
| filler_size=$(wc -c < /tmp/filler) | |
| echo "filler size: ${filler_size} bytes" | |
| cat > /tmp/stub/oasdiff <<'STUB' | |
| #!/bin/sh | |
| # Emit a synthetic JSON changelog totalling >4 MB. printf is a | |
| # POSIX builtin so neither the variable assignment via $(cat ...) | |
| # nor the final printf '%s' "$json" goes through execve. | |
| filler=$(cat /tmp/filler) | |
| printf '[{"id":"big-1","text":"%s","level":3},{"id":"big-2","text":"%s","level":3}]' "$filler" "$filler" | |
| STUB | |
| chmod +x /tmp/stub/oasdiff | |
| mkdir -p /tmp/run | |
| export GITHUB_REF=refs/pull/123/merge | |
| export GITHUB_REPOSITORY=foo/bar | |
| export GITHUB_SHA=deadbeef | |
| export GITHUB_BASE_REF=main | |
| export GITHUB_STEP_SUMMARY=/tmp/run/step-summary | |
| cat > /tmp/run/event.json <<EVT | |
| {"pull_request":{"head":{"sha":"deadbeef"},"base":{"sha":"baadcafe"}}} | |
| EVT | |
| export GITHUB_EVENT_PATH=/tmp/run/event.json | |
| export PATH=/tmp/stub:$PATH | |
| set +e | |
| out=$(./pr-comment/entrypoint.sh \ | |
| 'specs/base.yaml' 'specs/revision.yaml' \ | |
| '' '' '' '' '' '' 2>&1) | |
| rc=$? | |
| set -e | |
| echo "--- entrypoint output (truncated) ---" | |
| echo "$out" | head -c 2000 | |
| echo "--- exit code: $rc ---" | |
| if [ "$rc" -ne 0 ]; then | |
| echo "FAIL: expected exit 0 (script should reach the no-token skip after building the JSON payload), got $rc" >&2 | |
| echo "If the message contains 'jq: Argument list too long', the regression has returned: \$changes is being passed via argv (--argjson) instead of stdin." >&2 | |
| exit 1 | |
| fi | |
| if ! echo "$out" | grep -q "No oasdiff-token provided"; then | |
| echo "FAIL: script aborted before reaching the no-token skip; the jq payload-construction step likely blew up on the multi-MB changes array" >&2 | |
| exit 1 | |
| fi | |
| echo "PASS" | |
| pr_comment_curl_handles_large_payload: | |
| runs-on: ubuntu-latest | |
| name: Test pr-comment curl POST survives a multi-MB payload | |
| # Companion to pr_comment_handles_large_payload. The previous test | |
| # exercises the jq payload-construction path with an empty | |
| # oasdiff_token, which short-circuits past the curl step. This one | |
| # exercises the curl step itself by providing a non-empty token and | |
| # stubbing both oasdiff (multi-MB changelog source) and curl (verifies | |
| # it received the payload via stdin, not via argv). Catches the | |
| # regression class where `curl -d "$payload"` puts a multi-MB body on | |
| # argv and aborts with `curl: Argument list too long` after the jq | |
| # invocation already succeeded — the same ARG_MAX trap one layer | |
| # down. The fix pipes the payload through stdin via `--data-binary @-`. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Stub oasdiff + curl, run entrypoint with a fake token | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/stub /tmp/run | |
| # Same filler strategy as the jq test: dd | tr produces a | |
| # ~2 MB string of 'a' characters and exits 0 cleanly under | |
| # pipefail. | |
| dd if=/dev/zero bs=1024 count=2000 status=none | tr '\0' 'a' > /tmp/filler | |
| cat > /tmp/stub/oasdiff <<'STUB' | |
| #!/bin/sh | |
| filler=$(cat /tmp/filler) | |
| printf '[{"id":"big-1","text":"%s","level":3},{"id":"big-2","text":"%s","level":3}]' "$filler" "$filler" | |
| STUB | |
| chmod +x /tmp/stub/oasdiff | |
| # Stub curl: consume the POST body from stdin, record its | |
| # byte count to a side file the assertions below can read, | |
| # and emit the success-response shape the entrypoint expects | |
| # from `-s -w "\n%{http_code}"` (body then a newline then the | |
| # HTTP status code). | |
| cat > /tmp/stub/curl <<'STUB' | |
| #!/bin/sh | |
| body=$(cat) | |
| printf '%s' "$body" | wc -c > /tmp/run/curl-bytes | |
| printf '{"review_token":"stub-token-uuid"}\n200\n' | |
| STUB | |
| chmod +x /tmp/stub/curl | |
| export GITHUB_REF=refs/pull/123/merge | |
| export GITHUB_REPOSITORY=foo/bar | |
| export GITHUB_SHA=deadbeef | |
| export GITHUB_BASE_REF=main | |
| export GITHUB_STEP_SUMMARY=/tmp/run/step-summary | |
| cat > /tmp/run/event.json <<EVT | |
| {"pull_request":{"head":{"sha":"deadbeef"},"base":{"sha":"baadcafe"}}} | |
| EVT | |
| export GITHUB_EVENT_PATH=/tmp/run/event.json | |
| # Note: jq must remain the system jq (not stubbed). PATH puts | |
| # our stubs first; we only override `oasdiff` and `curl`. | |
| export PATH=/tmp/stub:$PATH | |
| set +e | |
| out=$(./pr-comment/entrypoint.sh \ | |
| 'specs/base.yaml' 'specs/revision.yaml' \ | |
| '' '' '' 'stub-oasdiff-token' '' '' 2>&1) | |
| rc=$? | |
| set -e | |
| echo "--- entrypoint output (truncated) ---" | |
| echo "$out" | head -c 2000 | |
| echo "--- exit code: $rc ---" | |
| if [ "$rc" -ne 0 ]; then | |
| echo "FAIL: expected exit 0, got $rc" >&2 | |
| if echo "$out" | grep -q "curl: Argument list too long"; then | |
| echo "The regression has returned: \$payload is being passed via curl argv (-d) instead of stdin (--data-binary @-)." >&2 | |
| fi | |
| exit 1 | |
| fi | |
| if [ ! -f /tmp/run/curl-bytes ]; then | |
| echo "FAIL: curl stub was never invoked; script aborted before reaching the POST" >&2 | |
| exit 1 | |
| fi | |
| curl_bytes=$(cat /tmp/run/curl-bytes | tr -d ' ') | |
| echo "curl received: ${curl_bytes} bytes" | |
| if [ "$curl_bytes" -lt 4000000 ]; then | |
| echo "FAIL: curl stub received only ${curl_bytes} bytes; expected >4 MB (proves payload made it through stdin)" >&2 | |
| exit 1 | |
| fi | |
| echo "PASS" | |
| pr_comment_repo_limit_warns_without_failing: | |
| runs-on: ubuntu-latest | |
| name: Test pr-comment surfaces a repo-limit 402 as a warning without failing | |
| # When the service returns 402 repo_limit_reached (this PR's repo is beyond | |
| # the tenant's plan cap), the action should surface a warning annotation + | |
| # step summary with the upgrade link but NOT fail the workflow (exit 0) -- a | |
| # billing limit shouldn't break the customer's merge gate. Guards against | |
| # regressing to the generic non-2xx branch, which dumps raw JSON and exits 1. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Stub oasdiff + curl (402), run entrypoint with a fake token | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/stub /tmp/run | |
| cat > /tmp/stub/oasdiff <<'STUB' | |
| #!/bin/sh | |
| printf '[{"id":"c1","text":"removed endpoint","level":3}]' | |
| STUB | |
| chmod +x /tmp/stub/oasdiff | |
| # Stub curl: ignore the POST body, emit the 402 repo_limit_reached | |
| # response (body, newline, status code) the entrypoint parses. | |
| cat > /tmp/stub/curl <<'STUB' | |
| #!/bin/sh | |
| cat >/dev/null | |
| printf '{"code":"repo_limit_reached","message":"Your oasdiff plan covers 5 repositories. acme/widgets is a new repository beyond that limit — upgrade at https://www.oasdiff.com/pricing to add it.","owner":"acme","repo":"widgets","max_repos":5,"upgrade_url":"https://www.oasdiff.com/pricing"}\n402\n' | |
| STUB | |
| chmod +x /tmp/stub/curl | |
| export GITHUB_REF=refs/pull/123/merge | |
| export GITHUB_REPOSITORY=acme/widgets | |
| export GITHUB_SHA=deadbeef | |
| export GITHUB_BASE_REF=main | |
| export GITHUB_STEP_SUMMARY=/tmp/run/step-summary | |
| : > "$GITHUB_STEP_SUMMARY" | |
| cat > /tmp/run/event.json <<EVT | |
| {"pull_request":{"head":{"sha":"deadbeef"},"base":{"sha":"baadcafe"}}} | |
| EVT | |
| export GITHUB_EVENT_PATH=/tmp/run/event.json | |
| export PATH=/tmp/stub:$PATH | |
| set +e | |
| out=$(./pr-comment/entrypoint.sh \ | |
| 'specs/base.yaml' 'specs/revision.yaml' \ | |
| '' '' '' 'stub-oasdiff-token' '' '' 2>&1) | |
| rc=$? | |
| set -e | |
| echo "--- entrypoint output ---"; echo "$out" | |
| echo "--- exit code: $rc ---" | |
| echo "--- step summary ---"; cat "$GITHUB_STEP_SUMMARY" | |
| if [ "$rc" -ne 0 ]; then | |
| echo "FAIL: expected exit 0 (warn-don't-fail), got $rc" >&2 | |
| exit 1 | |
| fi | |
| if ! echo "$out" | grep -q "::warning title=oasdiff plan limit reached::"; then | |
| echo "FAIL: missing repo-limit warning annotation" >&2 | |
| exit 1 | |
| fi | |
| if ! grep -q "oasdiff plan limit reached" "$GITHUB_STEP_SUMMARY"; then | |
| echo "FAIL: repo-limit step summary not written" >&2 | |
| exit 1 | |
| fi | |
| if echo "$out" | grep -q "oasdiff-service returned HTTP"; then | |
| echo "FAIL: fell through to the generic non-2xx branch" >&2 | |
| exit 1 | |
| fi | |
| echo "PASS" | |
| pr_comment_expired_trial_warns_without_failing: | |
| runs-on: ubuntu-latest | |
| name: Test pr-comment surfaces an expired-trial 402 as a warning without failing | |
| # When the tenant.Validate middleware returns 402 subscription_expired (the | |
| # tenant's trial or subscription has lapsed), the action should surface a | |
| # graceful "trial expired" warning + step summary with the renewal link but | |
| # NOT fail the workflow (exit 0). Guards against regressing to the generic | |
| # non-2xx branch, which dumps raw JSON and exits 1 with an opaque error. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Stub oasdiff + curl (402 subscription_expired), run entrypoint | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/stub /tmp/run | |
| cat > /tmp/stub/oasdiff <<'STUB' | |
| #!/bin/sh | |
| printf '[{"id":"c1","text":"removed endpoint","level":3}]' | |
| STUB | |
| chmod +x /tmp/stub/oasdiff | |
| # Stub curl: ignore the POST body, emit the 402 subscription_expired | |
| # response (body, newline, status code) the middleware returns. | |
| cat > /tmp/stub/curl <<'STUB' | |
| #!/bin/sh | |
| cat >/dev/null | |
| printf '{"code":"subscription_expired","message":"Your oasdiff trial or subscription has expired. Renew at https://www.oasdiff.com/pricing to continue using oasdiff Pro.","upgrade_url":"https://www.oasdiff.com/pricing"}\n402\n' | |
| STUB | |
| chmod +x /tmp/stub/curl | |
| export GITHUB_REF=refs/pull/123/merge | |
| export GITHUB_REPOSITORY=acme/widgets | |
| export GITHUB_SHA=deadbeef | |
| export GITHUB_BASE_REF=main | |
| export GITHUB_STEP_SUMMARY=/tmp/run/step-summary | |
| : > "$GITHUB_STEP_SUMMARY" | |
| cat > /tmp/run/event.json <<EVT | |
| {"pull_request":{"head":{"sha":"deadbeef"},"base":{"sha":"baadcafe"}}} | |
| EVT | |
| export GITHUB_EVENT_PATH=/tmp/run/event.json | |
| export PATH=/tmp/stub:$PATH | |
| set +e | |
| out=$(./pr-comment/entrypoint.sh \ | |
| 'specs/base.yaml' 'specs/revision.yaml' \ | |
| '' '' '' 'stub-oasdiff-token' '' '' 2>&1) | |
| rc=$? | |
| set -e | |
| echo "--- entrypoint output ---"; echo "$out" | |
| echo "--- exit code: $rc ---" | |
| echo "--- step summary ---"; cat "$GITHUB_STEP_SUMMARY" | |
| if [ "$rc" -ne 0 ]; then | |
| echo "FAIL: expected exit 0 (warn-don't-fail), got $rc" >&2 | |
| exit 1 | |
| fi | |
| if ! echo "$out" | grep -q "::warning title=oasdiff trial or subscription expired::"; then | |
| echo "FAIL: missing expired-trial warning annotation" >&2 | |
| exit 1 | |
| fi | |
| if ! grep -q "oasdiff trial or subscription expired" "$GITHUB_STEP_SUMMARY"; then | |
| echo "FAIL: expired-trial step summary not written" >&2 | |
| exit 1 | |
| fi | |
| if echo "$out" | grep -q "oasdiff-service returned HTTP"; then | |
| echo "FAIL: fell through to the generic non-2xx branch" >&2 | |
| exit 1 | |
| fi | |
| echo "PASS" |