Skip to content

Merge pull request #185 from oasdiff/docs/link-diff-action #286

Merge pull request #185 from oasdiff/docs/link-diff-action

Merge pull request #185 from oasdiff/docs/link-diff-action #286

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"