From e36f4510a6f0805d46cb068c7fdf6cb5cba748c5 Mon Sep 17 00:00:00 2001 From: contra Date: Wed, 30 Sep 2026 13:21:17 +0300 Subject: [PATCH 1/2] =?UTF-8?q?test:=20self-test=20for=200.1.6=20=E2=80=94?= =?UTF-8?q?=20routing=20smoke=20on=20exit=20classification,=20Google=20red?= =?UTF-8?q?irect=20logged,=20env=20fixture,=20skip=20guard;=20default=20ve?= =?UTF-8?q?rsion=200.1.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/env-variant/action.yml | 50 ++++++++++++++++++++++++ .github/workflows/self-test.yml | 54 ++++++++++++++++++++++---- README.md | 2 +- action.yml | 2 +- 4 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 .github/actions/env-variant/action.yml diff --git a/.github/actions/env-variant/action.yml b/.github/actions/env-variant/action.yml new file mode 100644 index 0000000..d87e69a --- /dev/null +++ b/.github/actions/env-variant/action.yml @@ -0,0 +1,50 @@ +# TEST FIXTURE ONLY — not the public action. Identical to ../../../action.yml +# except that it has no api-key input and sets nothing named SHELLROUTE_API_KEY +# itself. It tests whether the calling step's `env:` reaches a composite +# action's run steps as inherited process environment. +name: 'Shellroute Run (env fixture)' +description: 'Test fixture: same as the public action, without the api-key input' + +inputs: + country: + description: 'ISO country code (e.g. US, DE, GB)' + required: true + command: + description: 'Command to run through the proxy' + required: true + version: + description: 'Shellroute CLI version to install' + required: false + default: '0.1.6' + +runs: + using: 'composite' + steps: + - name: Wiring check (prints set/unset, never the value) + shell: bash + run: | + if [ -n "${SHELLROUTE_API_KEY:-}" ]; then echo "SHELLROUTE_API_KEY: set"; else echo "SHELLROUTE_API_KEY: unset"; fi + + - name: Install shellroute + shell: bash + env: + SR_VERSION: ${{ inputs.version }} + run: | + if ! [[ "$SR_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid version format: $SR_VERSION" + exit 1 + fi + npm install -g "shellroute@$SR_VERSION" + shellroute --version + + - name: Run through proxy + shell: bash + env: + SR_COUNTRY: ${{ inputs.country }} + SR_COMMAND: ${{ inputs.command }} + run: | + if ! [[ "$SR_COUNTRY" =~ ^[A-Za-z]{2}$ ]]; then + echo "::error::Invalid country code: $SR_COUNTRY" + exit 1 + fi + shellroute run --no-stat "$SR_COUNTRY" -- bash -c "$SR_COMMAND" diff --git a/.github/workflows/self-test.yml b/.github/workflows/self-test.yml index 9ca946c..258d16a 100644 --- a/.github/workflows/self-test.yml +++ b/.github/workflows/self-test.yml @@ -1,6 +1,10 @@ name: Self-test on: workflow_dispatch +# One manual run = five metered sessions at most (US, DE, compound, exit-code, +# env fixture). Output is limited to status codes, redirect URLs, the CLI +# version, and set/unset. Logs are public. + jobs: test: runs-on: ubuntu-latest @@ -8,25 +12,35 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Smoke — US routed request + # Routing smoke test, not an application regression: the assertion is the + # ipinfo classification of the exit address. Google's redirect is printed + # for information only — on 2026-09-30 a DE exit received a 302 to a + # consent page rather than the 301 ?hl=de seen in earlier dated examples. + - name: Smoke — US routed request (input contract) + id: us_check + if: always() uses: ./ with: api-key: ${{ secrets.SHELLROUTE_API_KEY }} country: US command: | - CODE=$(curl -sI --connect-timeout 15 --max-time 30 https://store.google.com/ -o /dev/null -w '%{http_code}') - echo "status=$CODE" - [ "$CODE" = "301" ] + C=$(curl -s --connect-timeout 15 --max-time 30 https://ipinfo.io/country) + echo "exit country: $C" + curl -sI --connect-timeout 15 --max-time 30 https://store.google.com/ -o /dev/null -w 'store.google.com: %{http_code} %{redirect_url}\n' + [ "$C" = "US" ] - - name: Smoke — DE routed request + - name: Smoke — DE routed request (input contract) + id: de_check + if: always() uses: ./ with: api-key: ${{ secrets.SHELLROUTE_API_KEY }} country: DE command: | - CODE=$(curl -sI --connect-timeout 15 --max-time 30 https://store.google.com/ -o /dev/null -w '%{http_code}') - echo "status=$CODE" - [ "$CODE" = "301" ] + C=$(curl -s --connect-timeout 15 --max-time 30 https://ipinfo.io/country) + echo "exit country: $C" + curl -sI --connect-timeout 15 --max-time 30 https://store.google.com/ -o /dev/null -w 'store.google.com: %{http_code} %{redirect_url}\n' + [ "$C" = "DE" ] - name: Compound command — both parts routed uses: ./ @@ -70,3 +84,27 @@ jobs: exit 1 fi echo "Country validation confirmed" + + # Contract question: does the calling step's env reach a composite action + # as inherited shell environment? Test-only fixture, no api-key input. + - name: Env fixture — inherited SHELLROUTE_API_KEY, US route + id: env_variant + if: always() + continue-on-error: true + uses: ./.github/actions/env-variant + env: + SHELLROUTE_API_KEY: ${{ secrets.SHELLROUTE_API_KEY }} + with: + country: US + command: 'C=$(curl -s --connect-timeout 15 --max-time 30 https://ipinfo.io/country); echo "exit country: $C"; [ "$C" = "US" ]' + + - name: Report both contract variants (fail if either was skipped) + if: always() + shell: bash + run: | + echo "input contract: US=${{ steps.us_check.outcome }} DE=${{ steps.de_check.outcome }}" + echo "env fixture: ${{ steps.env_variant.outcome }}" + for o in "${{ steps.us_check.outcome }}" "${{ steps.de_check.outcome }}" "${{ steps.env_variant.outcome }}"; do + [ "$o" = "skipped" ] && { echo "a contract variant was skipped — not a valid comparison"; exit 1; } + done + [ "${{ steps.us_check.outcome }}" = "success" ] && [ "${{ steps.de_check.outcome }}" = "success" ] || exit 1 diff --git a/README.md b/README.md index 8b65a5d..f88a912 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Compound commands stay inside the same route: | `api-key` | yes | — | Shellroute API key (pass `${{ secrets.SHELLROUTE_API_KEY }}`) | | `country` | yes | — | Two-letter country code (US, DE, GB, etc.) | | `command` | yes | — | Command to run through the proxy | -| `version` | no | `0.1.5` | Shellroute CLI version to install | +| `version` | no | `0.1.6` | Shellroute CLI version to install | ## Output diff --git a/action.yml b/action.yml index 08daba6..000be15 100644 --- a/action.yml +++ b/action.yml @@ -17,7 +17,7 @@ inputs: version: description: 'Shellroute CLI version to install' required: false - default: '0.1.5' + default: '0.1.6' outputs: exit-code: From 3a3a2c9087bc8939d7985796d5134cdfa6be6a31 Mon Sep 17 00:00:00 2001 From: contra Date: Wed, 30 Sep 2026 13:24:32 +0300 Subject: [PATCH 2/2] test: require exit-code 42 for propagation; env fixture classifies outcome into outputs; explicit verdicts in job summary --- .github/actions/env-variant/action.yml | 46 ++++++++++++++++++++++---- .github/workflows/self-test.yml | 43 +++++++++++++++++++----- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/.github/actions/env-variant/action.yml b/.github/actions/env-variant/action.yml index d87e69a..3db464e 100644 --- a/.github/actions/env-variant/action.yml +++ b/.github/actions/env-variant/action.yml @@ -1,7 +1,11 @@ -# TEST FIXTURE ONLY — not the public action. Identical to ../../../action.yml -# except that it has no api-key input and sets nothing named SHELLROUTE_API_KEY -# itself. It tests whether the calling step's `env:` reaches a composite -# action's run steps as inherited process environment. +# TEST FIXTURE ONLY — not the public action. Same install and run shape as +# ../../../action.yml, but with no api-key input and nothing named +# SHELLROUTE_API_KEY set inside; it tests whether the calling step's `env:` +# reaches a composite action's run steps as inherited process environment. +# +# It is a diagnostic: after installing, it never fails the step. It classifies +# what happened into outputs so the workflow can report the env contract +# unambiguously instead of inferring it from a red or green badge. name: 'Shellroute Run (env fixture)' description: 'Test fixture: same as the public action, without the api-key input' @@ -17,13 +21,27 @@ inputs: required: false default: '0.1.6' +outputs: + wiring: + description: 'set if SHELLROUTE_API_KEY was inherited, unset otherwise (value never printed)' + value: ${{ steps.wiring.outputs.wiring }} + result: + description: 'routed-ok | route-mismatch | auth-missing | auth-rejected | other' + value: ${{ steps.run.outputs.result }} + rc: + description: 'exit code of shellroute run' + value: ${{ steps.run.outputs.rc }} + runs: using: 'composite' steps: - name: Wiring check (prints set/unset, never the value) + id: wiring shell: bash run: | - if [ -n "${SHELLROUTE_API_KEY:-}" ]; then echo "SHELLROUTE_API_KEY: set"; else echo "SHELLROUTE_API_KEY: unset"; fi + if [ -n "${SHELLROUTE_API_KEY:-}" ]; then w=set; else w=unset; fi + echo "SHELLROUTE_API_KEY: $w" + echo "wiring=$w" >> "$GITHUB_OUTPUT" - name: Install shellroute shell: bash @@ -37,7 +55,8 @@ runs: npm install -g "shellroute@$SR_VERSION" shellroute --version - - name: Run through proxy + - name: Run through proxy and classify the outcome + id: run shell: bash env: SR_COUNTRY: ${{ inputs.country }} @@ -47,4 +66,17 @@ runs: echo "::error::Invalid country code: $SR_COUNTRY" exit 1 fi - shellroute run --no-stat "$SR_COUNTRY" -- bash -c "$SR_COMMAND" + set +e + out=$(shellroute run --no-stat "$SR_COUNTRY" -- bash -c "$SR_COMMAND" 2>&1) + rc=$? + set -e + printf '%s\n' "$out" + if [ "$rc" -eq 0 ]; then result=routed-ok + elif printf '%s' "$out" | grep -q "Not authenticated"; then result=auth-missing + elif printf '%s' "$out" | grep -q "Invalid API key"; then result=auth-rejected + elif printf '%s' "$out" | grep -q "exit country:"; then result=route-mismatch + else result=other + fi + echo "env fixture result: $result (rc=$rc)" + echo "result=$result" >> "$GITHUB_OUTPUT" + echo "rc=$rc" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/self-test.yml b/.github/workflows/self-test.yml index 258d16a..90a98fc 100644 --- a/.github/workflows/self-test.yml +++ b/.github/workflows/self-test.yml @@ -58,14 +58,19 @@ jobs: country: US command: 'exit 42' - - name: Verify child failure surfaced + - name: Verify child exit code 42 propagated shell: bash run: | + echo "outcome=${{ steps.fail_check.outcome }} exit-code=${{ steps.fail_check.outputs.exit-code }}" if [ "${{ steps.fail_check.outcome }}" != "failure" ]; then - echo "Expected failure, got: ${{ steps.fail_check.outcome }}" + echo "Expected the step to fail, got: ${{ steps.fail_check.outcome }}" exit 1 fi - echo "Exit-code propagation confirmed" + if [ "${{ steps.fail_check.outputs.exit-code }}" != "42" ]; then + echo "Step failed but not with the child's exit code 42 — propagation NOT confirmed" + exit 1 + fi + echo "Exit-code propagation confirmed: 42" - name: Invalid country rejected id: bad_country @@ -86,7 +91,9 @@ jobs: echo "Country validation confirmed" # Contract question: does the calling step's env reach a composite action - # as inherited shell environment? Test-only fixture, no api-key input. + # as inherited shell environment? Test-only fixture, no api-key input. It + # is a diagnostic: it classifies its outcome into outputs and never fails + # after install, so the verdict below is explicit, not read off the badge. - name: Env fixture — inherited SHELLROUTE_API_KEY, US route id: env_variant if: always() @@ -98,13 +105,31 @@ jobs: country: US command: 'C=$(curl -s --connect-timeout 15 --max-time 30 https://ipinfo.io/country); echo "exit country: $C"; [ "$C" = "US" ]' - - name: Report both contract variants (fail if either was skipped) + # Verdicts. The job status reflects the PUBLIC contract steps only + # (US/DE smoke, exit code, country validation). The env contract verdict is + # printed and written to the job summary; a green job never implies a + # working env contract, and a wiring or auth problem is not a routing one. + - name: Verdicts (public contract sets job status; env contract is reported, never inferred) if: always() shell: bash run: | - echo "input contract: US=${{ steps.us_check.outcome }} DE=${{ steps.de_check.outcome }}" - echo "env fixture: ${{ steps.env_variant.outcome }}" - for o in "${{ steps.us_check.outcome }}" "${{ steps.de_check.outcome }}" "${{ steps.env_variant.outcome }}"; do + us="${{ steps.us_check.outcome }}"; de="${{ steps.de_check.outcome }}" + envo="${{ steps.env_variant.outcome }}"; wiring="${{ steps.env_variant.outputs.wiring }}" + result="${{ steps.env_variant.outputs.result }}"; rc="${{ steps.env_variant.outputs.rc }}" + case "$result" in + routed-ok) envv="ENV_CONTRACT: verified — inherited env reached the CLI and the US route classified as US" ;; + route-mismatch) envv="ENV_CONTRACT: verified (auth ok) — but exit classification was not US (routing/classification issue, not a contract issue)" ;; + auth-missing) [ "$wiring" = set ] && envv="ENV_CONTRACT: NOT verified — key was inherited by the step but the CLI reported Not authenticated" || envv="ENV_CONTRACT: inconclusive — wiring unset, fixture or secret problem" ;; + auth-rejected) envv="ENV_CONTRACT: inconclusive — key delivered but rejected by the service (secret problem, not transport)" ;; + *) envv="ENV_CONTRACT: inconclusive — fixture outcome=$envo wiring=$wiring result=$result rc=$rc" ;; + esac + { + echo "## Self-test verdicts" + echo "- PUBLIC_CONTRACT (api-key input): US=$us DE=$de" + echo "- $envv" + echo "- A red US/DE step with 'exit country:' in its log is a routing/classification failure; with 'Not authenticated' it is an auth/wiring failure." + } | tee -a "$GITHUB_STEP_SUMMARY" + for o in "$us" "$de" "$envo"; do [ "$o" = "skipped" ] && { echo "a contract variant was skipped — not a valid comparison"; exit 1; } done - [ "${{ steps.us_check.outcome }}" = "success" ] && [ "${{ steps.de_check.outcome }}" = "success" ] || exit 1 + [ "$us" = "success" ] && [ "$de" = "success" ]