test(e2e): journey-based policy + anti-regrowth guard + migrate all non-Ordered specs (senm.4, y59n.4, 0cyn) #600
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-senm.4"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Migrates all e2e specs to Ordered journeys; adds AST policy check guard wired into lint+CI; amends CLAUDE.md with API+browser e2e policy. Closes bead bookshelf-senm.4, bookshelf-y59n.4, bookshelf-0cyn on merge.
- Add E2E Testing Policy section to CLAUDE.md (repo root) codifying: - API e2e: ~8 Ordered journeys only; no per-spec BeforeEach(ResetDB); multi-Expect relaxation for journey Its documented - Browser e2e: real-Chromium-only journeys; Vitest for controller logic; API tests for server HTML; go-rod absolute-deadline gotcha documented - Add scripts/e2e_policy_check/main.go: Go AST checker that fails if any e2e/api/ or e2e/browser/ top-level Describe is missing Ordered - Wire e2e-policy-check into make lint and CI lint job - Migrate e2e/api/audit_log_test.go to Ordered journey (5 It-steps, fresh env) - Migrate e2e/api/comics_test.go to Ordered journey (2 It-steps, fresh env) - Migrate e2e/api/bookdrop_extract_pattern_test.go to single Ordered journey (8 It-steps replacing 2 Describes with per-spec BeforeEach) - Migrate e2e/browser/bookdrop_extract_pattern_test.go: add Ordered, BeforeEach -> BeforeAll, cleanup via DeferCleanup - Migrate e2e/browser/metadata_fetch_cover_apply_test.go: add Ordered, move ResetDB + seed to BeforeAll - Fix helpers_test.go seedBook: use env.DB (not suiteEnv.DB) so journey-local envs get the fixture in the right database Closes bead bookshelf-senm.4, bookshelf-y59n.4, bookshelf-0cyn on merge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Security Review — PR #600 (bd-bookshelf-senm.4)
Scope: e2e policy lock-in — CLAUDE.md doc edits, new AST guard script (
scripts/e2e_policy_check/main.go), Makefile + ci.yml wiring, migrated e2e tests. No production code.Guard script (
scripts/e2e_policy_check/main.go)go/ast,go/parser,go/token,os,path/filepath,strings,fmt. Noos/execor equivalent.os.Stdout/os.Stderr. Noos.Create,os.WriteFile, orioutil.WriteFile.filepath.Join(root, "e2e", "api")/filepath.Join(root, "e2e", "browser")constructs a fixed two-level path.os.ReadDiron the resulting dir walks only that directory (non-recursive). A maliciousrootarg could point elsewhere, but in CIrootdefaults to.(no arg passed in ci.yml step) and the binary only reads_test.gofiles — no write path exists.entry.Name()fromos.ReadDiris a bare filename (no..);filepath.Join(dir, entry.Name())cannot escape the fixed directories._test.go.ci.yml change
go run ./scripts/e2e_policy_checkruns in the existinglintjob context with the same permissions as every other lint step — no elevated perms, no new secret bindings.go runcompiles and runs entirely from the repo working tree; no network fetch, no untrusted code pulled in.Migrated e2e tests
e2e/api/ande2e/browser/— dev/test code only.env.AuthClient/env.BaseURLpattern fromtestutil.internal/,cmd/,static/,templates/) was modified — confirmed viagit diff --name-only.CLAUDE.md
REVIEW VERDICT: 0 blocker, 0 major, 0 minor
CODE REVIEW — PR #600 (bd-bookshelf-senm.4)
Phase 0: DEMO Verification
No DEMO block in this PR. The bead is a policy/tooling PR (AST guard + spec migration) — there is no user-facing runtime feature to demo. CI green on all 6 jobs is the verification signal.
Phase 1 + Phase 2: Findings
[MINOR] CLAUDE.md:E2E Testing Policy — Budget numbers are stale
~8 journeysfor API and~6 ordered interactive journeysfor browser are already out of date: the branch has 13 top-level Describes ine2e/api/and 12 ine2e/browser/(counting across all files withgrep -c "^var _ = Describe"). The numbers were accurate at some earlier point in the epic but this PR adds audit_log + comics + bookdrop_extract_pattern to API and bookdrop_extract_pattern + metadata_fetch_cover_apply to browser. These are approximate (~) so they are not wrong per se, but a reader expecting "~8" and finding 13 will be confused. Suggest updating to~13/~12or dropping the exact count and just saying "a small, bounded set."[MINOR]
.claude/rules/project-conventions.md:364-366 — Old per-endpoint rule contradicts new policyproject-conventions.mdline 366 still says "Every HTTP route gets at least one e2e test ine2e/api/covering status code, content type, and key body assertions." This directly contradicts the newCLAUDE.mdE2E Testing Policy that bans per-endpoint e2e specs and replaces them with unit tests. CLAUDE.md says it is the "Authoritative policy" and that project-conventions "live[s] in.claude/rules/project-conventions.md" for the full framework — but the old rule is still there and an implementer reading only project-conventions.md will write a fresh per-endpoint test and the guard will NOT catch it (because the guard only checks the Ordered modifier, not the test structure). The contradiction should be resolved by removing or replacing that line with a pointer to CLAUDE.md.[MINOR]
scripts/e2e_policy_check/main.go— No self-test for the guardThe guard has no
*_test.goalongside it. A future change tocheckFileorhasOrderedcould silently regress (false negatives). A small table-driven test with two synthetic source strings — one Ordered, one not — would catch this. Not a blocker (the guard is simple enough to audit by reading), but it leaves correctness entirely on code review.Guard Correctness Analysis (explicit per the review request)
Detection logic: The guard walks
f.Decls(top-level file declarations only), finds*ast.GenDeclwithtoken.VAR, then*ast.ValueSpecwhere all names are_, then*ast.CallExprwhereFunis an*ast.Identnamed"Describe". It then checks whether any argument is an*ast.Identnamed"Ordered". This is correct for the project's conventions.No false positives: All 13 API Describes and 12 browser Describes in the branch include
Orderedas a bare identifier. The guard passes all of them.var _ = BeforeSuite(...)andvar _ = testutil.RegisterLogDumpFor(...)in suite files are correctly skipped (notDescribe).No false negatives: A new file with
var _ = Describe("...", func() { ... })(noOrdered) would be flagged. The logic is correct.Scope is correct: Only
e2e/api/ande2e/browser/are scanned.e2e/testutil/server_test.gohas a non-Ordered Describe for the connection-budget guard — correctly excluded from the scan since it is ine2e/testutil/, note2e/api/ore2e/browser/.One theoretical gap (not actionable):
isDescribe()only catches bareDescribeidentifiers, notginkgo.Describe(a*ast.SelectorExpr). A developer who imported ginkgo without a dot import and wroteginkgo.Describe("x", func(){})would bypass the check. However, all existing and new e2e files use. "github.com/onsi/ginkgo/v2"(dot import), makingOrderedandDescribebare identifiers. This is an acceptable trade-off given the project's consistent convention. Documenting this assumption in the guard's package comment would be a minor improvement.CI wiring:
make e2e-policy-checkinvokesgo run ./scripts/e2e_policy_checkand exits non-zero on violation. It is called frommake lintand as a standalone CI step in.forgejo/workflows/ci.yml(lint job).os.Exit(1)on violations andos.Exit(2)on parse errors both cause CI failure. Correct.helpers_test.go fix: All 12
suiteEnv.DB.calls changed toenv.DB.. This is correct —seedBooknow operates on the journey-localTestEnv.DBrather than the suite-level DB, which is necessary for journeys that boot their own fresh DB viaNewSuiteServerWithConfig. The fix prevents cross-contamination between per-journey DBs.REVIEW VERDICT: 0 blocker, 0 major, 3 minor
7dcf16c6cafeb7bbb15f