Repository navigation
Share test fakes across packages and fix the bugs they hid - #235
Merged
Merged
Conversation
Each package hand-rolled its own fakes for db, NATS, LLM client, storage, analyzer, prompt registry, vectordb and retention hold store. They had drifted apart: some panicked on an unscripted call, some returned nil, some silently succeeded, and several accepted inputs the real implementation rejects. That hid real bugs. Each interface now has one shared fake in a <pkg>test package (dbtest, llmclienttest, natsbustest, storagetest, analyzertest, prompttest, vectordbtest, retentiontest). They follow one convention: <Method>Func fields, an ErrUnscripted error naming the field to set (never a panic), deep-copied recorders, and rejection of what production rejects. The retention fake runs the same HoldStoreContract as the Postgres store so it cannot drift from it. DeterministicLLM moves to testspecs. Assisted-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Trevor Vaughan <peiriannydd@gmail.com>
The collector's result handler passed its handler context to Dispatcher.Redispatch. pkg/natsbus wrapHandler builds that context from context.Background() plus a span, so it never carries a tenant, and NATSDispatcher.Redispatch requires one. In production every retry failed with ErrNoTenant and the task was reported as ErrRetryExhausted. The collector now rebuilds the tenant context from the validated subject tenant. natsbustest.Fake.Deliver and Sub.Handle passed the caller's context to handlers, so specs saw a tenant that production never provides. That is what hid this bug. They now deliver with a tenantless context, as the real wrapHandler does. Assisted-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Trevor Vaughan <peiriannydd@gmail.com>
Many suites repeated near-identical It blocks that differed only in inputs, and integration suites copied the same role-DSN and password bootstrap code. Collapse them into DescribeTable/Entry, keeping every assertion and spec count, and add dbtest.RoleDSN, SetRolePasswordFromEnv and TestID so integration suites share one implementation. RoleDSN refuses malformed DSNs without echoing them, because url.Parse errors quote the superuser password. Along the way: - Remove two pathological slow specs (per-iteration RSA keygen and an unbounded memdriver traversal) that took most of the unit run. - Make empty-argument gateway specs assert that no backend is reached, so each entry fails if validation is removed. - Replace fixed sleeps in natsbus integration specs with shared helpers and fix a metrics wait race. - Skip dot-directories in the pkg/db tenant-scope source walk, which failed when a concurrent build removed a .go-tmp directory. - Do not print url.Parse errors for TEST_DATABASE_DSN in the migrate integration test. Assisted-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Trevor Vaughan <peiriannydd@gmail.com>
Every gateway handler returned a backend error as-is, so driver detail (SQL, role names, paths, dial addresses) reached clients as CodeUnknown or through codes like Unavailable (CWE-209). publicError is now an allowlist applied to every handler and the AdminService mount. Only caller-actionable codes pass through unchanged. Everything else is logged once and replaced by an opaque message naming a server-generated error reference, not the trace ID: the caller can set the trace ID via traceparent. Retryable codes keep their code so client retry logic still works. rpcserver's panic recovery uses the same reference. The catalog service classified not-found by raw error text and turned every storage failure into NotFound, which leaked storage detail through the allowlist. It now uses sentinels. The auth interceptor now counts and logs certificates refused for an invalid tenant ID instead of refusing them silently. Assisted-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Trevor Vaughan <peiriannydd@gmail.com>
- docs(agents): add a "Test doubles and fixtures" section - Lists the shared fake package for each pkg/ interface and when a local fake is still allowed - Covers when to embed a fake and override only some methods, the contract specs stateful fakes need, and shared fixtures - The sweep found that hand-rolled fakes hid real bugs, so new tests should use the shared fakes - docs(agents): add table, rapid.Check speed and no-sleep rules for tests - docs(agents): require publicError for gateway dependency errors - Also: classify errors with sentinels and errors.Is, never by matching message text - docs(gateway): say publicError points at an error reference, not the trace - docs(testfakes): fix stale notes in the shared fake package docs - dbtest documents gaps: no use-after-Commit check, and Scan rejects unsupported destinations - vectordbtest no longer says Stored returns a deep copy Assisted-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Trevor Vaughan <peiriannydd@gmail.com>
✅
|
| Descriptor | Linter | Files | Fixed | Errors | Max errors | Warnings | Elapsed time |
|---|---|---|---|---|---|---|---|
| ✅ ACTION | actionlint | 2 | 0 | 0 | 0.41s | ||
| ✅ ACTION | zizmor | 2 | 0 | 0 | 1.31s | ||
| ✅ COPYPASTE | jscpd | yes | no | no | 1.33s | ||
| ✅ GO | golangci-lint | yes | no | no | 98.01s | ||
| markdownlint | 2 | 12 | 0 | 0.64s | |||
| markdown-table-formatter | 2 | 1 | 0 | 0.16s | |||
| ✅ YAML | prettier | 3 | 0 | 0 | 0.33s | ||
| ✅ YAML | yamllint | 3 | 0 | 0 | 0.36s |
Detailed Issues
⚠️ MARKDOWN / markdown-table-formatter - 1 error
2 files contain markdown tables to format:
- README.md
- docs/dev/design-principles.md
⚠️ MARKDOWN / markdownlint - 12 errors
docs/dev/design-principles.md:21:9 error MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"]
docs/dev/design-principles.md:21:16 error MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"]
docs/dev/design-principles.md:21:22 error MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"]
docs/dev/design-principles.md:21:1 error MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"]
docs/dev/design-principles.md:21:9 error MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"]
docs/dev/design-principles.md:21:16 error MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"]
README.md:116:8 error MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"]
README.md:116:21 error MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"]
README.md:116:31 error MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"]
README.md:116:1 error MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"]
README.md:116:8 error MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"]
README.md:116:21 error MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"]
Notices
API_SPECTRAL, MAKEFILE_CHECKMAKE, MARKDOWN_MARKDOWN_LINK_CHECK, REPOSITORY_GITLEAKS, REPOSITORY_KICS. See Removed linters to find their replacements.
See detailed reports in MegaLinter artifacts
Set VALIDATE_ALL_CODEBASE: true in mega-linter.yml to validate all sources, not only the diff

Show us your support by starring ⭐ the repository
Ginkgo's --timeout includes compiling the suite with --race and coverage. On a slow CI runner the compile alone exceeded 120s, so integration-grpc on PR #235 failed with "Suite did not run because the timeout elapsed" and zero specs run. 300s is the existing tier used by the other suites. Assisted-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Trevor Vaughan <peiriannydd@gmail.com>
Scoping PR fuzzing to changed packages still took too long for a PR workflow: a broad PR touches most packages, and each target pays a fixed build cost on top of its 30s. A shorter FUZZ_TIME would cut the fuzzing but not the build, and would add little over the seed corpora and committed crash inputs that `task test` already replays. fuzz-nightly.yml remains the one place that fuzzes. Drop fetch-depth: 2, which only the PR fuzz step needed, and record the decision in the workflows, README and AGENTS.md so it is not re-added by accident. Assisted-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Trevor Vaughan <peiriannydd@gmail.com>
trevor-vaughan
marked this pull request as ready for review
October 27, 2026 21:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Each package had its own hand-rolled fakes for db, NATS, the LLM client, storage and the other
pkg/interfaces. Over time they drifted apart. Some panicked on a call nobody scripted, some returned nil, some silently succeeded, and several accepted input the real implementation rejects. This PR replaces them with one shared fake per interface. Moving to the shared fakes exposed two production bugs, and this PR fixes both:Dispatcher.Redispatch. That context never carries a tenant, so every retry failed withErrNoTenantand showed up asErrRetryExhausted.The PR also collapses duplicated specs into
DescribeTabletables, removes the slowest unit specs, and writes the resulting conventions into AGENTS.md.Changes
Shared test fakes (
a2d6f35)dbtest,llmclienttest,natsbustest,storagetest,analyzertest,prompttest,vectordbtest,retentiontest. All of them work the same way:<Method>Funcfields.ErrUnscripted, which names the field to set. It never panics.retentiontest.HoldStoreContractruns against both the fake and the Postgres hold store, so the two cannot drift apart.DeterministicLLMmoves tointernal/testspecs.Fix: collector redispatch loses the tenant (
f644e37)Redispatch.natsbustest.Fake.DeliverandSub.Handlenow give handlers a context without a tenant, the same asnatsbuswrapHandlerdoes in production. The old fake passed the caller's context through, which is why no spec caught this bug.Fix: gateway withholds unclassified errors (
360f15c)publicError(internal/gateway/helpers.go) is an allowlist. Every GatewayService handler calls it, and an interceptor applies it to the AdminService mount.InvalidArgument,NotFound,AlreadyExists,FailedPrecondition,OutOfRange,PermissionDenied,Unauthenticated,ResourceExhausted,CanceledandUnimplemented.Unavailable,DeadlineExceededandAbortedkeep their code so client retry logic still works, but the message is replaced.Internal.traceparent.rpcserver's panic recovery returns the same kind of error reference.NotFound. That leaked storage detail through the allowlist. It now uses sentinel errors witherrors.Is.Unauthenticatedwith an actionable message instead ofInternal. The refusal is also counted in the auth-failure metric and logged.Test cleanup (
1611fb6)Itblocks intoDescribeTable/Entry. Every assertion is kept.dbtest.RoleDSN,SetRolePasswordFromEnvandTestIDso integration suites stop copying the same role-DSN and password setup.RoleDSNrefuses a malformed DSN without echoing it, becauseurl.Parseerrors quote the superuser password. The migrate integration test also stops printingurl.Parseerrors forTEST_DATABASE_DSN.natsbusintegration specs with shared wait helpers, and fix a race in a metrics wait.pkg/dbtenant-scope source walk now skips dot-directories. It used to fail when a concurrent build removed a.go-tmpdirectory.Docs (
7dc6353)rapid.Checkbodies, bounding traversals withMaxDepth, and usingEventually/Consistentlyinstead oftime.Sleep. It now requirespublicErrorfor gateway dependency errors and sentinel errors instead of matching error text.doc.gonotes in the fake packages, for example the gaps indbtestand the "deep copy" claim forvectordbtest.Stored.Client-visible changes
Unknown,InternalorDataLoss, and plain errors, now reach clients asInternalwith an opaque message and an error reference.Unavailable,DeadlineExceededandAbortedkeep their code, but their message is replaced the same way. To find the cause, search the gateway logs for theerror_refvalue.Unauthenticatedinstead ofInternal.Testing
task check(lint, unit, race)dbtesthelpers:task test:integration:<name>🤖 Generated with Claude Code