# TEST REPORT: Stripe Access Drift Check (n8n) v1.0.0

Run by `N8N_BIN=n8n245/node_modules/.bin/n8n N8N3_CMD=n8n3-run/start.sh N8N3_HOST_DATA=n8n3-run/data N8N3_DATA=/data DENO=~/.local/bin/deno bash tests/run_all.sh`;
full output in `tests/logs/run-20261102.txt`. Started 2026-11-02 07:39:42 GMT, finished 07:43:48 GMT. Box: Linux, Node v24.22.1, Python 3.13.5, Deno 2.9.8.

**Result: 6/6 steps, `FAILED STEPS: 0`.** 20/20 unit tests, 21/21 static checks, rebuilt workflow identical to the shipped file,
1/1 SQL test, **33/33 end-to-end checks on real n8n 2.45.7 and 33/33 on real n8n 3.0.1**.

## What is real and what is not
- **Real:** the shipped workflow JSON imported with the n8n CLI into a throwaway instance, credentials imported, workflow published,
  `n8n start`, then driven over HTTP (webhook) and by its own Schedule Trigger. n8n 2.45.7 from npm; n8n 3.0.1 is the official
  `n8nio/n8n:3.0.1` image unpacked and run in a chroot (its own Node and CLI; not Docker's network/cgroup setup).
  The Supabase export view SQL on real Postgres (PGlite) with Supabase-like roles.
- **Mocked:** Stripe (`tests/mock_services.py`: `GET /v1/subscriptions` with real `starting_after` cursor pagination, 2 per page,
  Bearer key check, records every request, rejects POST), the app export endpoint, and Slack (records posts).
- **Not tested:** a live Stripe account (real rate limits, >5,000 subscriptions, expanded fields), a real Supabase REST endpoint
  (the `apikey: sb_secret_…` header is from Supabase's docs: https://supabase.com/docs/guides/api/creating-routes), real Slack,
  n8n Cloud, queue mode, Postgres-backed n8n.

## 1. Unit: `tests/compare.test.js` (20)
Clean match (trialing/past_due count as paying, canceled without access is fine); PAID_BUT_BLOCKED critical and first; MEDIUM-only ->
DRIFT_REVIEW; duplicate subs / unlinked customer / shared customer id / access without link; overrides skipped; `'t'`/`1`/`'true'`
truthy; expanded `customer` object; same subscription on two pages counted once; CHECK_FAILED for a Stripe error body, livemode
mismatch, empty export, invalid `priceToPlan`; export as raw JSON text (`[]`, HTML login page, `{"message":...}` all fail safely);
CHECK_SUSPECT for "most users unpaid" and "most paying subs unlinked" with all findings withheld; just under the threshold real findings
are reported; truncated list withholds revoke-type findings but keeps PAID_BUT_BLOCKED; Slack list cap; custom grant statuses; inputs not mutated.

## 2. Static: `tests/validate_workflow.py` (21)
Unique names/ids, all connections valid, every node reachable from a trigger; three triggers (hourly, header-auth POST webhook, manual);
Stripe call is `GET /v1/subscriptions` with `status=all`; only the Slack node POSTs; error outputs + 3 retries on both requests;
pagination by `starting_after` until `has_more` is false with a page cap; export fetched as text; no `alwaysOutputData`; Compare node
embeds `src/compare.js`; both Code nodes parse; no API keys/Slack URLs in the file; credentials by name only; ships unpublished; Config has every documented setting.

## 3. SQL: `tests/sql/export_view.test.ts` (1)
`setup/supabase-export-view.sql`: service_role reads the 5 expected columns (null `has_access` -> false); anon and authenticated get
"permission denied". Removing the `revoke` line makes this test fail (checked by hand once, 2 Nov).

## 4. End to end on real n8n: `tests/e2e_n8n.py` (33 per version)
Import/publish; webhook without or with a wrong token -> 403 and no Stripe/Slack calls; clean data -> 200 CLEAN across 3 Stripe pages
with `starting_after`, correct counts, no Slack post; only `GET /v1/subscriptions` ever hits Stripe; drift data -> 409 DRIFT_URGENT
with the exact 5 expected findings, PAID_BUT_BLOCKED first, override skipped, one Slack post naming customer and subscription;
Stripe 401 -> 502 CHECK_FAILED, one "could not run" post, 3 tries, export not called; export 500 -> 502 naming the export, alerted
once; empty export -> CHECK_FAILED; test-mode data vs `expectLivemode=true` -> CHECK_FAILED; wrong account -> CHECK_SUSPECT, findings
withheld, alert explains why; page cap -> 3 requests, truncated, unseen customers not reported; manual/CLI run (`n8n execute`);
**an every-minute copy of the workflow published and left alone: the Schedule Trigger fired by itself and posted the alert**, read-only.

## Failures seen during development (all fixed before the logged run)
1. **Export error double-alerted** (2.45.7): with `alwaysOutputData` the HTTP node also emitted an empty item on its success output, so
   both "could not run" and "0 rows" fired. Fixed by fetching the export as text (one item, even for `[]`) and removing `alwaysOutputData`.
2. **Wrong account still produced DRIFT_URGENT:** the other account's paying customers showed as PAID_NOT_LINKED. The suspect valve now
   withholds all findings, and a second valve covers "most paying subs match no user".
3. `n8n execute` needs a start node ("Missing node to start execution"); added a Manual Trigger, and the schedule is now tested by a real tick.
4. Unit tests found a crash: an invalid `priceToPlan` referenced `appName` before it was defined (JS temporal dead zone). Fixed.
5. **n8n 3.0.1:** `n8n import:credentials` on a never-started instance fails with "No active encryption key found" (3.x creates the
   key on the first main-instance start). Test harness starts n8n once first; SETUP.md tells CLI users the same.

Nothing was published, deployed or sent; no real Stripe, Supabase or Slack account was used.

## Pre-launch re-run 2026-11-02
Same command as above; full output in `tests/logs/run-20261102-prelaunch.txt`, started 08:01:35 GMT, finished 08:05:45 GMT. **6/6 steps, `FAILED STEPS: 0`**: 20/20 unit, 21/21 static, rebuilt workflow identical, 1/1 SQL, **33/33 end-to-end on real n8n 2.45.7 and 33/33 on real n8n 3.0.1**. Counts unchanged. Stripe and Slack still mocked. Box-specific install paths in the run command above were shortened.
Extra pre-launch check: `setup/supabase-export-view.sql` on a throwaway **native PostgreSQL 17.11** (not PGlite): 4/4 checks passed (applies; service_role reads 2 rows with null `has_access` as false; anon and authenticated get permission denied). Stripe test mode was not run: no test-mode Stripe account or key was available; Stripe stays mocked.
