diff --git a/.github/workflows/test-breaking.yaml b/.github/workflows/test-breaking.yaml new file mode 100644 index 0000000..08d16db --- /dev/null +++ b/.github/workflows/test-breaking.yaml @@ -0,0 +1,380 @@ +name: 'Test oasdiff breaking action' +on: + pull_request: + push: +jobs: + oasdiff_breaking: + runs-on: ubuntu-latest + name: Test breaking changes + env: + OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "1 changes: 1 error, 0 warning, 0 info" + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running breaking action + id: test_breaking_changes + uses: ./breaking + with: + base: 'specs/base.yaml' + revision: 'specs/revision-breaking.yaml' + output-to-file: 'breaking.txt' + - name: Test breaking changes action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_breaking_changes.outputs.breaking }} + $delimiter + ) + if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 + exit 1 + fi + - name: Test breaking changes action output to file + run: | + if [ ! -s breaking.txt ]; then + echo "Breaking changes file doesn't exist or is empty" + exit 1 + fi + output=$(cat breaking.txt | head -n 1) + if [[ "${output}" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 + exit 1 + fi + oasdiff_breaking_fail_on: + runs-on: ubuntu-latest + name: Test fail on breaking changes + env: + OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "2 changes: 0 error, 2 warning, 0 info" + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running breaking action + id: test_breaking_changes + uses: ./breaking + with: + base: 'specs/base.yaml' + revision: 'specs/revision-breaking-warn.yaml' + output-to-file: 'breaking.txt' + fail-on: 'ERR' + - name: Test breaking changes action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_breaking_changes.outputs.breaking }} + $delimiter + ) + if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 + exit 1 + fi + - name: Test breaking changes action output to file + run: | + if [ ! -s breaking.txt ]; then + echo "Breaking changes file doesn't exist or is empty" + exit 1 + fi + output=$(cat breaking.txt | head -n 1) + if [[ "${output}" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 + exit 1 + fi + oasdiff_breaking_matching_delimiter_not_found: + runs-on: ubuntu-latest + name: Test breaking action with petsotre to validate no error of unable to process file command 'output' successfully and invalid value and matching delimiter not found + env: + OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "9 changes: 6 error, 3 warning, 0 info" + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running breaking action with petsotre to validate no error of unable to process file command 'output' successfully and invalid value and matching delimiter not found + id: test_breaking_changes_matching_delimiter_not_found + uses: ./breaking + with: + base: 'specs/petstore-base.yaml' + revision: 'specs/petstore-revision.yaml' + - name: Test breaking changes action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_breaking_changes_matching_delimiter_not_found.outputs.breaking }} + $delimiter + ) + if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 + exit 1 + fi + oasdiff_breaking_composed: + runs-on: ubuntu-latest + name: Test breaking action with composed option + env: + OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "1 changes: 1 error, 0 warning, 0 info" + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running breaking action with composed option + id: test_breaking_composed + uses: ./breaking + with: + base: 'specs/glob/base/*.yaml' + revision: 'specs/glob/revision/*.yaml' + composed: true + - name: Test breaking action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_breaking_composed.outputs.breaking }} + $delimiter + ) + if [[ ! "$output" =~ "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]]; then + echo "Expected '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT', instead got '$output'" >&2 + exit 1 + fi + oasdiff_breaking_deprecation: + runs-on: ubuntu-latest + name: Test breaking changes with deprecation + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Set date for deprecated specs + run: | + # Deprecate Beta in 14 days + sed -ie "s/{{SUNSET_DATE_BETA}}/$(date --date="14 day" "+%Y-%m-%d")/" specs/base-deprecation.yaml + # Deprecate Stable in 21 days + sed -ie "s/{{SUNSET_DATE_STABLE}}/$(date --date="21 day" "+%Y-%m-%d")/" specs/base-deprecation.yaml + - name: Running OpenAPI Spec check breaking action + id: test_breaking_deprecations + uses: ./breaking + with: + base: specs/base.yaml + revision: specs/base-deprecation.yaml + deprecation-days-beta: 14 + deprecation-days-stable: 21 + oasdiff_breaking_flatten_allof: + runs-on: ubuntu-latest + name: Test breaking action with flatten-allof option + env: + OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "1 changes: 1 error, 0 warning, 0 info" + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running breaking action with flatten-allof option + id: test_breaking_flatten_allof + uses: ./breaking + with: + base: 'specs/base-allof.yaml' + revision: 'specs/revision-allof.yaml' + flatten-allof: true + - name: Test breaking action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_breaking_flatten_allof.outputs.breaking }} + $delimiter + ) + if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 + exit 1 + fi + oasdiff_breaking_err_ignore: + runs-on: ubuntu-latest + name: Test breaking action with err-ignore option + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running breaking action with err-ignore option + id: test_breaking_err_ignore + uses: ./breaking + with: + base: 'specs/base.yaml' + revision: 'specs/revision-breaking.yaml' + err-ignore: 'specs/err-ignore.txt' + - name: Test breaking changes are suppressed + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_breaking_err_ignore.outputs.breaking }} + $delimiter + ) + if [ "$output" != "No breaking changes" ]; then + echo "Expected 'No breaking changes' but got '$output'" >&2 + exit 1 + fi + + # --------------------------------------------------------------------- + # .oasdiff.yaml config-file support — verify all three free actions + # pick up .oasdiff.yaml from the repo root automatically (via the CLI's + # cwd-based config-file lookup; the runner mounts $GITHUB_WORKSPACE as + # the container's WORKDIR). + # --------------------------------------------------------------------- + + oasdiff_breaking_yaml_config_fail_on: + runs-on: ubuntu-latest + name: Test breaking action picks up fail-on from .oasdiff.yaml + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Drop .oasdiff.yaml at repo root + run: | + cat > .oasdiff.yaml <&2 + exit 1 + fi + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_yaml_fail_on.outputs.breaking }} + $delimiter + ) + # Even when fail-on triggers, the entrypoint should still render + # the report. Check that the breaking output is non-empty and + # matches the same shape as a normal fail-on-input run. + if [ "$output" != "1 changes: 1 error, 0 warning, 0 info" ]; then + echo "Expected '1 changes: 1 error, 0 warning, 0 info' to be rendered alongside fail-on, got: '$output'" >&2 + exit 1 + fi + + oasdiff_breaking_yaml_config_err_ignore: + runs-on: ubuntu-latest + name: Test breaking action picks up err-ignore from .oasdiff.yaml + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Drop .oasdiff.yaml at repo root + run: | + cat > .oasdiff.yaml <&2 + exit 1 + fi + + breaking_free_review_url_preserves_https_base: + runs-on: ubuntu-latest + name: Test breaking free review URL keeps the https:// scheme on URL-style base + # Regression test for the bug class fixed in pr-comment (#120): the naive + # base_path=$(echo "$base" | sed 's/.*://') strips "https:" from a + # URL-shaped base and leaves a broken "//raw.github..." in the free + # /review link, which the page renders as a misleading access-denied + # screen. The strip_ref_prefix helper passes URLs through unchanged. + steps: + - uses: actions/checkout@v6 + - name: Stub oasdiff to report a change so the notice fires + run: | + set -euo pipefail + mkdir -p /tmp/stub + cat > /tmp/stub/oasdiff <<'STUB' + #!/bin/sh + echo '1 breaking change(s)' + STUB + chmod +x /tmp/stub/oasdiff + + mkdir -p /tmp/run + export GITHUB_REPOSITORY=oasdiff-test/test + export GITHUB_SHA=deadbeef + export GITHUB_BASE_REF=main + export GITHUB_OUTPUT=/tmp/run/github-output + export GITHUB_STEP_SUMMARY=/tmp/run/step-summary + cat > /tmp/run/event.json <&1) + echo "--- entrypoint output ---" + echo "$out" + + if ! echo "$out" | grep -q "::notice::.*breaking changes"; then + echo "FAIL: review-URL notice line missing" >&2 + exit 1 + fi + notice=$(echo "$out" | grep "::notice::.*breaking changes") + if echo "$notice" | grep -q 'base_file=https%3A%2F%2Fraw.githubusercontent.com'; then + echo "PASS: full https://raw... URL preserved in base_file" + elif echo "$notice" | grep -q 'base_file=%2F%2Fraw.githubusercontent.com'; then + echo "FAIL: 'https:' was stripped from base, the strip_ref_prefix URL guard is missing" >&2 + echo "notice line: $notice" >&2 + exit 1 + else + echo "FAIL: base_file= param did not contain the raw.githubusercontent.com host as expected" >&2 + echo "notice line: $notice" >&2 + exit 1 + fi + + breaking_free_review_url_strips_git_ref_prefix: + runs-on: ubuntu-latest + name: "Test breaking free review URL strips origin/main: prefix from git-ref base" + # Companion to breaking_free_review_url_preserves_https_base: a git-ref + # base (origin/main:openapi.yaml) must still have its prefix stripped so + # the /review page receives just the path. + steps: + - uses: actions/checkout@v6 + - name: Stub oasdiff to report a change so the notice fires + run: | + set -euo pipefail + mkdir -p /tmp/stub + cat > /tmp/stub/oasdiff <<'STUB' + #!/bin/sh + echo '1 breaking change(s)' + STUB + chmod +x /tmp/stub/oasdiff + + mkdir -p /tmp/run + export GITHUB_REPOSITORY=foo/bar + export GITHUB_SHA=deadbeef + export GITHUB_BASE_REF=main + export GITHUB_OUTPUT=/tmp/run/github-output + export GITHUB_STEP_SUMMARY=/tmp/run/step-summary + cat > /tmp/run/event.json <&1) + echo "--- entrypoint output ---" + echo "$out" + + if ! echo "$out" | grep -q "::notice::.*breaking changes"; then + echo "FAIL: review-URL notice line missing" >&2 + exit 1 + fi + notice=$(echo "$out" | grep "::notice::.*breaking changes") + if echo "$notice" | grep -q 'base_file=multi-file%2Fopenapi.yaml'; then + echo "PASS: origin/main: prefix stripped from base" + else + echo "FAIL: base_file= did not have the git-ref prefix stripped" >&2 + echo "notice line: $notice" >&2 + exit 1 + fi + diff --git a/.github/workflows/test-changelog.yaml b/.github/workflows/test-changelog.yaml new file mode 100644 index 0000000..54ded37 --- /dev/null +++ b/.github/workflows/test-changelog.yaml @@ -0,0 +1,195 @@ +name: 'Test oasdiff changelog action' +on: + pull_request: + push: +jobs: + oasdiff_changelog: + runs-on: ubuntu-latest + name: Test generation of changelog + env: + OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "21 changes: 2 error, 4 warning, 15 info" + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running changelog action + id: test_changelog + uses: ./changelog + with: + base: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test1.yaml + revision: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test3.yaml + output-to-file: "changelog.txt" + - name: Test changelog action output + run: | + output=$(echo "${{steps.test_changelog.outputs.changelog}}" | head -n 1) + if [[ "${output}" != "${OASDIFF_ACTION_TEST_EXPECTED_OUTPUT}" ]]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 + exit 1 + fi + - name: Test changelog action output to file + run: | + if [ ! -s changelog.txt ]; then + echo "Changelog file doesn't exist or is empty" + exit 1 + fi + output=$(cat changelog.txt | head -n 1) + if [[ "${output}" != "${OASDIFF_ACTION_TEST_EXPECTED_OUTPUT}" ]]; then + echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 + exit 1 + fi + oasdiff_changelog_composed: + runs-on: ubuntu-latest + name: Test changelog action with composed option + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running changelog action with composed option + id: test_changelog_composed + uses: ./changelog + with: + base: 'specs/glob/base/*.yaml' + revision: 'specs/glob/revision/*.yaml' + composed: true + - name: Test changelog action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_changelog_composed.outputs.changelog }} + $delimiter + ) + if [[ ! "$output" =~ "1 changes: 1 error, 0 warning, 0 info" ]]; then + echo "Expected '1 changes: 1 error, 0 warning, 0 info', instead got '$output'" >&2 + exit 1 + fi + oasdiff_changelog_yaml_config_level: + runs-on: ubuntu-latest + name: Test changelog action picks up level from .oasdiff.yaml + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Drop .oasdiff.yaml at repo root + run: | + cat > .oasdiff.yaml <&2 + exit 1 + fi + + changelog_free_review_url_preserves_https_base: + runs-on: ubuntu-latest + name: Test changelog free review URL keeps the https:// scheme on URL-style base + # Regression test for the bug class fixed in pr-comment (#120): a + # URL-shaped base must survive intact in the free /review link rather + # than being mangled to "//raw.github..." by the naive ref-prefix strip. + steps: + - uses: actions/checkout@v6 + - name: Stub oasdiff to report a change so the notice fires + run: | + set -euo pipefail + mkdir -p /tmp/stub + cat > /tmp/stub/oasdiff <<'STUB' + #!/bin/sh + echo '1 change(s)' + STUB + chmod +x /tmp/stub/oasdiff + + mkdir -p /tmp/run + export GITHUB_REPOSITORY=oasdiff-test/test + export GITHUB_SHA=deadbeef + export GITHUB_BASE_REF=main + export GITHUB_OUTPUT=/tmp/run/github-output + export GITHUB_STEP_SUMMARY=/tmp/run/step-summary + cat > /tmp/run/event.json <&1) + echo "--- entrypoint output ---" + echo "$out" + + if ! echo "$out" | grep -q "::notice::.*API changes"; then + echo "FAIL: review-URL notice line missing" >&2 + exit 1 + fi + notice=$(echo "$out" | grep "::notice::.*API changes") + if echo "$notice" | grep -q 'base_file=https%3A%2F%2Fraw.githubusercontent.com'; then + echo "PASS: full https://raw... URL preserved in base_file" + elif echo "$notice" | grep -q 'base_file=%2F%2Fraw.githubusercontent.com'; then + echo "FAIL: 'https:' was stripped from base, the strip_ref_prefix URL guard is missing" >&2 + echo "notice line: $notice" >&2 + exit 1 + else + echo "FAIL: base_file= param did not contain the raw.githubusercontent.com host as expected" >&2 + echo "notice line: $notice" >&2 + exit 1 + fi + + changelog_free_review_url_strips_git_ref_prefix: + runs-on: ubuntu-latest + name: "Test changelog free review URL strips origin/main: prefix from git-ref base" + # Companion: git-ref base must have its prefix stripped to a bare path. + steps: + - uses: actions/checkout@v6 + - name: Stub oasdiff to report a change so the notice fires + run: | + set -euo pipefail + mkdir -p /tmp/stub + cat > /tmp/stub/oasdiff <<'STUB' + #!/bin/sh + echo '1 change(s)' + STUB + chmod +x /tmp/stub/oasdiff + + mkdir -p /tmp/run + export GITHUB_REPOSITORY=foo/bar + export GITHUB_SHA=deadbeef + export GITHUB_BASE_REF=main + export GITHUB_OUTPUT=/tmp/run/github-output + export GITHUB_STEP_SUMMARY=/tmp/run/step-summary + cat > /tmp/run/event.json <&1) + echo "--- entrypoint output ---" + echo "$out" + + if ! echo "$out" | grep -q "::notice::.*API changes"; then + echo "FAIL: review-URL notice line missing" >&2 + exit 1 + fi + notice=$(echo "$out" | grep "::notice::.*API changes") + if echo "$notice" | grep -q 'base_file=multi-file%2Fopenapi.yaml'; then + echo "PASS: origin/main: prefix stripped from base" + else + echo "FAIL: base_file= did not have the git-ref prefix stripped" >&2 + echo "notice line: $notice" >&2 + exit 1 + fi + diff --git a/.github/workflows/test-diff.yaml b/.github/workflows/test-diff.yaml new file mode 100644 index 0000000..0b4bd85 --- /dev/null +++ b/.github/workflows/test-diff.yaml @@ -0,0 +1,119 @@ +name: 'Test oasdiff diff action' +on: + pull_request: + push: +jobs: + oasdiff_diff: + runs-on: ubuntu-latest + name: Test diff action + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running diff action + id: test_ete + uses: ./diff + with: + base: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test1.yaml + revision: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test3.yaml + format: 'text' + output-to-file: 'diff.txt' + - name: Test diff action output to file + run: | + if [ ! -s diff.txt ]; then + echo "Diff file doesn't exist or is empty" + exit 1 + fi + oasdiff_diff_exclude_elements: + runs-on: ubuntu-latest + name: Test diff action with exclude-elements option + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running diff action with exclude-elements option + id: test_exclude_elements + uses: ./diff + with: + base: 'specs/base.yaml' + revision: 'specs/base-exclude-elements.yaml' + format: 'text' + exclude-elements: 'description,title,summary' + - name: Test diff action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_exclude_elements.outputs.diff }} + $delimiter + ) + if [ "$output" != "No changes" ]; then + echo "Expected output 'No changes' but got '$output'" >&2 + exit 1 + fi + oasdiff_diff_composed: + runs-on: ubuntu-latest + name: Test diff action with composed option + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Running diff action with composed option + id: test_composed + uses: ./diff + with: + base: 'specs/glob/base/*.yaml' + revision: 'specs/glob/revision/*.yaml' + format: 'text' + composed: true + - name: Test diff action output + run: | + delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') + output=$(cat <<-$delimiter + ${{ steps.test_composed.outputs.diff }} + $delimiter + ) + if [[ ! "$output" =~ "Deleted Endpoints: 1" ]]; then + echo "Expected 'Deleted Endpoints: 1' to be modified in diff, instead got '$output'" >&2 + exit 1 + fi + oasdiff_diff_yaml_config_exclude_elements: + runs-on: ubuntu-latest + name: Test diff action picks up exclude-elements from .oasdiff.yaml + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Drop .oasdiff.yaml at repo root + run: | + cat > .oasdiff.yaml <&2 + exit 1 + fi + + # --------------------------------------------------------------------- + # pr-comment entrypoint: oasdiff exit-code tolerance. + # + # The pr-comment script wraps the oasdiff changelog invocation so a + # non-zero exit (e.g. fail-on triggered from .oasdiff.yaml) does not + # abort the script under set -e. Real failures (no output) still + # abort. These two jobs run pr-comment/entrypoint.sh directly with a + # stubbed oasdiff on PATH so we don't need a Docker image, an + # oasdiff-token, or a reachable oasdiff-service. + # --------------------------------------------------------------------- + diff --git a/.github/workflows/test-pr-comment.yaml b/.github/workflows/test-pr-comment.yaml new file mode 100644 index 0000000..7b4ea56 --- /dev/null +++ b/.github/workflows/test-pr-comment.yaml @@ -0,0 +1,395 @@ +name: 'Test oasdiff pr-comment action' +on: + pull_request: + push: +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@v6 + - 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 <&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 "::notice::.*View API changes"; then + echo "FAIL: script aborted before emitting the review-page notice; the oasdiff fail-on tolerance fix is missing" >&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@v6 + - 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 <&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 + if echo "$out" | grep -q "::notice::.*View API changes"; then + echo "FAIL: script proceeded past the oasdiff failure; the early-abort branch is missing" >&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@v6 + - 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 <&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 "::notice::.*View API changes"; then + echo "FAIL: script aborted before emitting the review-page notice" >&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@v6 + - 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 <&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 + if ! echo "$out" | grep -q "::notice::.*View API changes"; then + echo "FAIL: script aborted before emitting the review-page notice" >&2 + exit 1 + fi + echo "PASS" + + pr_comment_free_review_url_preserves_https_base: + runs-on: ubuntu-latest + name: Test pr-comment free review URL keeps the https:// scheme on URL-style base + # Regression test for a bug surfaced by oasdiff-test/test#59: when the + # workflow passes a raw.githubusercontent.com URL as the base input + # (as the integration-test repo does), the entrypoint's + # base_path=$(echo "$base" | sed 's/.*://') + # stripped the "https:" prefix and left a broken "//raw.github..." + # URL. The free /review page then tried to GET that broken URL and + # rendered "access denied" with no useful diagnostic — because the + # generic access-denied screen masked the real cause (malformed URL). + # The fix passes URL-shaped inputs through unchanged. + steps: + - uses: actions/checkout@v6 + - name: Stub oasdiff to a no-changes spec + run: | + set -euo pipefail + mkdir -p /tmp/stub + cat > /tmp/stub/oasdiff <<'STUB' + #!/bin/sh + echo '[]' + STUB + chmod +x /tmp/stub/oasdiff + + mkdir -p /tmp/run + export GITHUB_REF=refs/pull/123/merge + export GITHUB_REPOSITORY=oasdiff-test/test + export GITHUB_SHA=deadbeef + export GITHUB_BASE_REF=main + export GITHUB_STEP_SUMMARY=/tmp/run/step-summary + cat > /tmp/run/event.json <&1) + echo "--- entrypoint output ---" + echo "$out" + + if ! echo "$out" | grep -q "::notice::.*View API changes"; then + echo "FAIL: notice line missing" >&2 + exit 1 + fi + notice=$(echo "$out" | grep "::notice::.*View API changes") + # The encoded base_file= param must contain the full URL, not the + # stripped "//raw..." form. https:// URL-encodes to https%3A%2F%2F. + if echo "$notice" | grep -q 'base_file=https%3A%2F%2Fraw.githubusercontent.com'; then + echo "PASS: full https://raw... URL preserved in base_file" + elif echo "$notice" | grep -q 'base_file=%2F%2Fraw.githubusercontent.com'; then + echo "FAIL: 'https:' was stripped from base — the strip_ref_prefix URL guard is missing" >&2 + echo "notice line: $notice" >&2 + exit 1 + else + echo "FAIL: base_file= param did not contain the raw.githubusercontent.com host as expected" >&2 + echo "notice line: $notice" >&2 + exit 1 + fi + + pr_comment_free_review_url_strips_git_ref_prefix: + runs-on: ubuntu-latest + name: "Test pr-comment free review URL strips origin/main: prefix from git-ref base" + # Companion to pr_comment_free_review_url_preserves_https_base: when + # the base input is git-ref form (origin/main:openapi.yaml), the + # prefix must still be stripped so the /review page receives just the + # path. The previous sed-based implementation handled this correctly; + # this test guards the case-branch refactor against breaking it. + steps: + - uses: actions/checkout@v6 + - name: Stub oasdiff to a no-changes spec + run: | + set -euo pipefail + mkdir -p /tmp/stub + cat > /tmp/stub/oasdiff <<'STUB' + #!/bin/sh + echo '[]' + 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 <&1) + echo "--- entrypoint output ---" + echo "$out" + + notice=$(echo "$out" | grep "::notice::.*View API changes") + # base_file should be just "multi-file/openapi.yaml" (URL-encoded slash), + # not the full "origin/main:multi-file/openapi.yaml". + if echo "$notice" | grep -q 'base_file=multi-file%2Fopenapi.yaml'; then + echo "PASS: origin/main: prefix stripped from base" + else + echo "FAIL: base_file= did not have the git-ref prefix stripped" >&2 + echo "notice line: $notice" >&2 + exit 1 + fi diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index b5f2ea4..0000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,863 +0,0 @@ -name: 'Test oasdiff actions' -on: - pull_request: - push: -jobs: - oasdiff_diff: - runs-on: ubuntu-latest - name: Test diff action - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running diff action - id: test_ete - uses: ./diff - with: - base: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test1.yaml - revision: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test3.yaml - format: 'text' - output-to-file: 'diff.txt' - - name: Test diff action output to file - run: | - if [ ! -s diff.txt ]; then - echo "Diff file doesn't exist or is empty" - exit 1 - fi - oasdiff_diff_exclude_elements: - runs-on: ubuntu-latest - name: Test diff action with exclude-elements option - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running diff action with exclude-elements option - id: test_exclude_elements - uses: ./diff - with: - base: 'specs/base.yaml' - revision: 'specs/base-exclude-elements.yaml' - format: 'text' - exclude-elements: 'description,title,summary' - - name: Test diff action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_exclude_elements.outputs.diff }} - $delimiter - ) - if [ "$output" != "No changes" ]; then - echo "Expected output 'No changes' but got '$output'" >&2 - exit 1 - fi - oasdiff_diff_composed: - runs-on: ubuntu-latest - name: Test diff action with composed option - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running diff action with composed option - id: test_composed - uses: ./diff - with: - base: 'specs/glob/base/*.yaml' - revision: 'specs/glob/revision/*.yaml' - format: 'text' - composed: true - - name: Test diff action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_composed.outputs.diff }} - $delimiter - ) - if [[ ! "$output" =~ "Deleted Endpoints: 1" ]]; then - echo "Expected 'Deleted Endpoints: 1' to be modified in diff, instead got '$output'" >&2 - exit 1 - fi - oasdiff_breaking: - runs-on: ubuntu-latest - name: Test breaking changes - env: - OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "1 changes: 1 error, 0 warning, 0 info" - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running breaking action - id: test_breaking_changes - uses: ./breaking - with: - base: 'specs/base.yaml' - revision: 'specs/revision-breaking.yaml' - output-to-file: 'breaking.txt' - - name: Test breaking changes action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_breaking_changes.outputs.breaking }} - $delimiter - ) - if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 - exit 1 - fi - - name: Test breaking changes action output to file - run: | - if [ ! -s breaking.txt ]; then - echo "Breaking changes file doesn't exist or is empty" - exit 1 - fi - output=$(cat breaking.txt | head -n 1) - if [[ "${output}" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 - exit 1 - fi - oasdiff_breaking_fail_on: - runs-on: ubuntu-latest - name: Test fail on breaking changes - env: - OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "2 changes: 0 error, 2 warning, 0 info" - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running breaking action - id: test_breaking_changes - uses: ./breaking - with: - base: 'specs/base.yaml' - revision: 'specs/revision-breaking-warn.yaml' - output-to-file: 'breaking.txt' - fail-on: 'ERR' - - name: Test breaking changes action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_breaking_changes.outputs.breaking }} - $delimiter - ) - if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 - exit 1 - fi - - name: Test breaking changes action output to file - run: | - if [ ! -s breaking.txt ]; then - echo "Breaking changes file doesn't exist or is empty" - exit 1 - fi - output=$(cat breaking.txt | head -n 1) - if [[ "${output}" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 - exit 1 - fi - oasdiff_breaking_matching_delimiter_not_found: - runs-on: ubuntu-latest - name: Test breaking action with petsotre to validate no error of unable to process file command 'output' successfully and invalid value and matching delimiter not found - env: - OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "9 changes: 6 error, 3 warning, 0 info" - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running breaking action with petsotre to validate no error of unable to process file command 'output' successfully and invalid value and matching delimiter not found - id: test_breaking_changes_matching_delimiter_not_found - uses: ./breaking - with: - base: 'specs/petstore-base.yaml' - revision: 'specs/petstore-revision.yaml' - - name: Test breaking changes action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_breaking_changes_matching_delimiter_not_found.outputs.breaking }} - $delimiter - ) - if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 - exit 1 - fi - oasdiff_breaking_composed: - runs-on: ubuntu-latest - name: Test breaking action with composed option - env: - OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "1 changes: 1 error, 0 warning, 0 info" - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running breaking action with composed option - id: test_breaking_composed - uses: ./breaking - with: - base: 'specs/glob/base/*.yaml' - revision: 'specs/glob/revision/*.yaml' - composed: true - - name: Test breaking action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_breaking_composed.outputs.breaking }} - $delimiter - ) - if [[ ! "$output" =~ "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]]; then - echo "Expected '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT', instead got '$output'" >&2 - exit 1 - fi - oasdiff_breaking_deprecation: - runs-on: ubuntu-latest - name: Test breaking changes with deprecation - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Set date for deprecated specs - run: | - # Deprecate Beta in 14 days - sed -ie "s/{{SUNSET_DATE_BETA}}/$(date --date="14 day" "+%Y-%m-%d")/" specs/base-deprecation.yaml - # Deprecate Stable in 21 days - sed -ie "s/{{SUNSET_DATE_STABLE}}/$(date --date="21 day" "+%Y-%m-%d")/" specs/base-deprecation.yaml - - name: Running OpenAPI Spec check breaking action - id: test_breaking_deprecations - uses: ./breaking - with: - base: specs/base.yaml - revision: specs/base-deprecation.yaml - deprecation-days-beta: 14 - deprecation-days-stable: 21 - oasdiff_changelog: - runs-on: ubuntu-latest - name: Test generation of changelog - env: - OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "21 changes: 2 error, 4 warning, 15 info" - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running changelog action - id: test_changelog - uses: ./changelog - with: - base: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test1.yaml - revision: https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test3.yaml - output-to-file: "changelog.txt" - - name: Test changelog action output - run: | - output=$(echo "${{steps.test_changelog.outputs.changelog}}" | head -n 1) - if [[ "${output}" != "${OASDIFF_ACTION_TEST_EXPECTED_OUTPUT}" ]]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 - exit 1 - fi - - name: Test changelog action output to file - run: | - if [ ! -s changelog.txt ]; then - echo "Changelog file doesn't exist or is empty" - exit 1 - fi - output=$(cat changelog.txt | head -n 1) - if [[ "${output}" != "${OASDIFF_ACTION_TEST_EXPECTED_OUTPUT}" ]]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '${output}'" >&2 - exit 1 - fi - oasdiff_changelog_composed: - runs-on: ubuntu-latest - name: Test changelog action with composed option - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running changelog action with composed option - id: test_changelog_composed - uses: ./changelog - with: - base: 'specs/glob/base/*.yaml' - revision: 'specs/glob/revision/*.yaml' - composed: true - - name: Test changelog action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_changelog_composed.outputs.changelog }} - $delimiter - ) - if [[ ! "$output" =~ "1 changes: 1 error, 0 warning, 0 info" ]]; then - echo "Expected '1 changes: 1 error, 0 warning, 0 info', instead got '$output'" >&2 - exit 1 - fi - oasdiff_breaking_flatten_allof: - runs-on: ubuntu-latest - name: Test breaking action with flatten-allof option - env: - OASDIFF_ACTION_TEST_EXPECTED_OUTPUT: "1 changes: 1 error, 0 warning, 0 info" - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running breaking action with flatten-allof option - id: test_breaking_flatten_allof - uses: ./breaking - with: - base: 'specs/base-allof.yaml' - revision: 'specs/revision-allof.yaml' - flatten-allof: true - - name: Test breaking action output - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_breaking_flatten_allof.outputs.breaking }} - $delimiter - ) - if [ "$output" != "$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT" ]; then - echo "Expected output '$OASDIFF_ACTION_TEST_EXPECTED_OUTPUT' but got '$output'" >&2 - exit 1 - fi - oasdiff_breaking_err_ignore: - runs-on: ubuntu-latest - name: Test breaking action with err-ignore option - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Running breaking action with err-ignore option - id: test_breaking_err_ignore - uses: ./breaking - with: - base: 'specs/base.yaml' - revision: 'specs/revision-breaking.yaml' - err-ignore: 'specs/err-ignore.txt' - - name: Test breaking changes are suppressed - run: | - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_breaking_err_ignore.outputs.breaking }} - $delimiter - ) - if [ "$output" != "No breaking changes" ]; then - echo "Expected 'No breaking changes' but got '$output'" >&2 - exit 1 - fi - - # --------------------------------------------------------------------- - # .oasdiff.yaml config-file support — verify all three free actions - # pick up .oasdiff.yaml from the repo root automatically (via the CLI's - # cwd-based config-file lookup; the runner mounts $GITHUB_WORKSPACE as - # the container's WORKDIR). - # --------------------------------------------------------------------- - - oasdiff_breaking_yaml_config_fail_on: - runs-on: ubuntu-latest - name: Test breaking action picks up fail-on from .oasdiff.yaml - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Drop .oasdiff.yaml at repo root - run: | - cat > .oasdiff.yaml <&2 - exit 1 - fi - delimiter=$(cat /proc/sys/kernel/random/uuid | tr -d '-') - output=$(cat <<-$delimiter - ${{ steps.test_yaml_fail_on.outputs.breaking }} - $delimiter - ) - # Even when fail-on triggers, the entrypoint should still render - # the report. Check that the breaking output is non-empty and - # matches the same shape as a normal fail-on-input run. - if [ "$output" != "1 changes: 1 error, 0 warning, 0 info" ]; then - echo "Expected '1 changes: 1 error, 0 warning, 0 info' to be rendered alongside fail-on, got: '$output'" >&2 - exit 1 - fi - - oasdiff_breaking_yaml_config_err_ignore: - runs-on: ubuntu-latest - name: Test breaking action picks up err-ignore from .oasdiff.yaml - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Drop .oasdiff.yaml at repo root - run: | - cat > .oasdiff.yaml <&2 - exit 1 - fi - - oasdiff_changelog_yaml_config_level: - runs-on: ubuntu-latest - name: Test changelog action picks up level from .oasdiff.yaml - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Drop .oasdiff.yaml at repo root - run: | - cat > .oasdiff.yaml <&2 - exit 1 - fi - - oasdiff_diff_yaml_config_exclude_elements: - runs-on: ubuntu-latest - name: Test diff action picks up exclude-elements from .oasdiff.yaml - steps: - - name: checkout - uses: actions/checkout@v6 - - name: Drop .oasdiff.yaml at repo root - run: | - cat > .oasdiff.yaml <&2 - exit 1 - fi - - # --------------------------------------------------------------------- - # pr-comment entrypoint: oasdiff exit-code tolerance. - # - # The pr-comment script wraps the oasdiff changelog invocation so a - # non-zero exit (e.g. fail-on triggered from .oasdiff.yaml) does not - # abort the script under set -e. Real failures (no output) still - # abort. These two jobs run pr-comment/entrypoint.sh directly with a - # stubbed oasdiff on PATH so we don't need a Docker image, an - # oasdiff-token, or a reachable oasdiff-service. - # --------------------------------------------------------------------- - - 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@v6 - - 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 <&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 "::notice::.*View API changes"; then - echo "FAIL: script aborted before emitting the review-page notice; the oasdiff fail-on tolerance fix is missing" >&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@v6 - - 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 <&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 - if echo "$out" | grep -q "::notice::.*View API changes"; then - echo "FAIL: script proceeded past the oasdiff failure; the early-abort branch is missing" >&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@v6 - - 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 <&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 "::notice::.*View API changes"; then - echo "FAIL: script aborted before emitting the review-page notice" >&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@v6 - - 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 <&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 - if ! echo "$out" | grep -q "::notice::.*View API changes"; then - echo "FAIL: script aborted before emitting the review-page notice" >&2 - exit 1 - fi - echo "PASS" - - pr_comment_free_review_url_preserves_https_base: - runs-on: ubuntu-latest - name: Test pr-comment free review URL keeps the https:// scheme on URL-style base - # Regression test for a bug surfaced by oasdiff-test/test#59: when the - # workflow passes a raw.githubusercontent.com URL as the base input - # (as the integration-test repo does), the entrypoint's - # base_path=$(echo "$base" | sed 's/.*://') - # stripped the "https:" prefix and left a broken "//raw.github..." - # URL. The free /review page then tried to GET that broken URL and - # rendered "access denied" with no useful diagnostic — because the - # generic access-denied screen masked the real cause (malformed URL). - # The fix passes URL-shaped inputs through unchanged. - steps: - - uses: actions/checkout@v6 - - name: Stub oasdiff to a no-changes spec - run: | - set -euo pipefail - mkdir -p /tmp/stub - cat > /tmp/stub/oasdiff <<'STUB' - #!/bin/sh - echo '[]' - STUB - chmod +x /tmp/stub/oasdiff - - mkdir -p /tmp/run - export GITHUB_REF=refs/pull/123/merge - export GITHUB_REPOSITORY=oasdiff-test/test - export GITHUB_SHA=deadbeef - export GITHUB_BASE_REF=main - export GITHUB_STEP_SUMMARY=/tmp/run/step-summary - cat > /tmp/run/event.json <&1) - echo "--- entrypoint output ---" - echo "$out" - - if ! echo "$out" | grep -q "::notice::.*View API changes"; then - echo "FAIL: notice line missing" >&2 - exit 1 - fi - notice=$(echo "$out" | grep "::notice::.*View API changes") - # The encoded base_file= param must contain the full URL, not the - # stripped "//raw..." form. https:// URL-encodes to https%3A%2F%2F. - if echo "$notice" | grep -q 'base_file=https%3A%2F%2Fraw.githubusercontent.com'; then - echo "PASS: full https://raw... URL preserved in base_file" - elif echo "$notice" | grep -q 'base_file=%2F%2Fraw.githubusercontent.com'; then - echo "FAIL: 'https:' was stripped from base — the strip_ref_prefix URL guard is missing" >&2 - echo "notice line: $notice" >&2 - exit 1 - else - echo "FAIL: base_file= param did not contain the raw.githubusercontent.com host as expected" >&2 - echo "notice line: $notice" >&2 - exit 1 - fi - - pr_comment_free_review_url_strips_git_ref_prefix: - runs-on: ubuntu-latest - name: "Test pr-comment free review URL strips origin/main: prefix from git-ref base" - # Companion to pr_comment_free_review_url_preserves_https_base: when - # the base input is git-ref form (origin/main:openapi.yaml), the - # prefix must still be stripped so the /review page receives just the - # path. The previous sed-based implementation handled this correctly; - # this test guards the case-branch refactor against breaking it. - steps: - - uses: actions/checkout@v6 - - name: Stub oasdiff to a no-changes spec - run: | - set -euo pipefail - mkdir -p /tmp/stub - cat > /tmp/stub/oasdiff <<'STUB' - #!/bin/sh - echo '[]' - 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 <&1) - echo "--- entrypoint output ---" - echo "$out" - - notice=$(echo "$out" | grep "::notice::.*View API changes") - # base_file should be just "multi-file/openapi.yaml" (URL-encoded slash), - # not the full "origin/main:multi-file/openapi.yaml". - if echo "$notice" | grep -q 'base_file=multi-file%2Fopenapi.yaml'; then - echo "PASS: origin/main: prefix stripped from base" - else - echo "FAIL: base_file= did not have the git-ref prefix stripped" >&2 - echo "notice line: $notice" >&2 - exit 1 - fi diff --git a/breaking/entrypoint.sh b/breaking/entrypoint.sh index cf77f4a..38869e9 100755 --- a/breaking/entrypoint.sh +++ b/breaking/entrypoint.sh @@ -109,8 +109,18 @@ if [ -n "$breaking_changes" ] && ! echo "$breaking_changes" | head -n 1 | grep - write_output "$(echo "$breaking_changes" | head -n 1)" "$breaking_changes" # Emit upgrade notice pointing to the free review page urlencode() { printf '%s' "$1" | jq -sRr @uri; } - base_path=$(echo "$base" | sed 's/.*://') - rev_path=$(echo "$revision" | sed 's/.*://') + # Strip the git-ref prefix ("origin/main:openapi.yaml" -> "openapi.yaml") + # but pass http(s):// URLs through unchanged. A naive `sed 's/.*://'` would + # also eat "https:" and emit a broken "//host/..." that the /review page + # can't fetch (it renders the misleading access-denied screen). + strip_ref_prefix() { + case "$1" in + http://*|https://*) printf '%s' "$1" ;; + *) printf '%s' "$1" | sed 's/.*://' ;; + esac + } + base_path=$(strip_ref_prefix "$base") + rev_path=$(strip_ref_prefix "$revision") owner="${GITHUB_REPOSITORY%%/*}" repo="${GITHUB_REPOSITORY#*/}" head_sha=$(jq -r '.pull_request.head.sha // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "") diff --git a/changelog/entrypoint.sh b/changelog/entrypoint.sh index 1b4db77..0088e0d 100755 --- a/changelog/entrypoint.sh +++ b/changelog/entrypoint.sh @@ -101,8 +101,18 @@ if [ -n "$output" ] && ! echo "$output" | head -n 1 | grep -q "^No "; then write_output "$output" # Emit upgrade notice pointing to the free review page urlencode() { printf '%s' "$1" | jq -sRr @uri; } - base_path=$(echo "$base" | sed 's/.*://') - rev_path=$(echo "$revision" | sed 's/.*://') + # Strip the git-ref prefix ("origin/main:openapi.yaml" -> "openapi.yaml") + # but pass http(s):// URLs through unchanged. A naive `sed 's/.*://'` would + # also eat "https:" and emit a broken "//host/..." that the /review page + # can't fetch (it renders the misleading access-denied screen). + strip_ref_prefix() { + case "$1" in + http://*|https://*) printf '%s' "$1" ;; + *) printf '%s' "$1" | sed 's/.*://' ;; + esac + } + base_path=$(strip_ref_prefix "$base") + rev_path=$(strip_ref_prefix "$revision") owner="${GITHUB_REPOSITORY%%/*}" repo="${GITHUB_REPOSITORY#*/}" head_sha=$(jq -r '.pull_request.head.sha // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "")