2f1803d1e678ebdecd80ce219cc263befa69dfd2
48
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8c0fb4ae42 |
Archive the suspended-plans and reconcile pool-lock changes
reconcile-takes-pool-lock (#193) and suspended-plans-stay-held (#148) move to openspec/changes/archive/ with every task ticked, and their deltas are synced into the main specs: - plan-transitions: 1 requirement modified (#193). - member-dashboard, member-entitlement-view, member-offer: 1 modified each; member-plan-upgrade: 1 renamed ("active" to "live" ladder attachments) and 1 modified; member-invoice-history: 2 added; member-product-discovery, stripe-subscription-creation: 1 added each; plan-enrollment-administration: 1 added, 3 modified; stripe-subscription-management: 1 added, 2 modified (#148). What #148's research and sandbox check taught, written into the pages that own each topic: - docs/models/plan-ladders-transitions.md: the member position's readers, all eight, each holding a suspended position as the organization's plan. - docs/models/payments-billing.md: pausing collection is not the paused status, so the plan keeps delivering. - docs/stripe.md: an expired pending upgrade's invoice is voided; a period-end cancellation stops retrying only that subscription's open invoice; only a subscription made outside the console reaches paused; paying on Stripe's page leaves the subscription's own default card in place; an uncollectible invoice's page still takes payment. - docs/testing.md: what a break record's wanted message must be, and how to drive a failed renewal in the Stripe sandbox. - status/design-feedback.md: the data model delivers through a past_due grace period, the console suspends at once (#255). - The archived design gains three reasons: why the immediate-cancel refusal comes first, the https-only redirect, and the paused badge's tone. |
||
|
|
715404500a |
Archive the runtime database user and the boot that serves first
serve-before-dependencies (#141) and runtime-database-user (#23) move to the archive, and their deltas are synced into openspec/specs. The two Purpose edits a delta cannot make are made here: temporal-connection's Purpose says start connects in the background and retries for the life of the process, and error-pages' names the 503. What the two investigations learned beyond each decision moves into the docs: - go-conventions.md: a new section on code that reaches a service that answers after the listener opens (holders, per-request reads, the refused write, retryloop, viper writes before the listener). - production-deployment.md: what a liveness probe requests, and why it must not wait on Temporal or the identity provider. - building-an-integration.md: declared paths skip the session layer; a completed workflow id runs again, and the guarded first update. - models/payments-billing.md: invariant 14 states the 503 and Discourse's failed rows; invariant 15 states the guarded update. - operator-ux-conventions.md: an error answer carries no HX- header. - database-management.md: why writers hold only the four DML privileges, identity columns and sequences, the qualified SQL the migration user runs, and sqlc alias lists for set-returning functions. - database-locks.md: rule 9, a row lock on a table the writer cannot update is taken in a SECURITY DEFINER function. - testing.md: privilege tests drop to console_runtime, race-only breaks as command records, the race run's -p 1, synctest for timer loops, and test workers off the console's task queue. Both archived design.md files gain the options weighed and the drafts the approved copy replaced. |
||
|
|
29520a8120 |
Set the Stripe key once at boot and read the boot mode everywhere
First section of #141. start assigns the stripe-go key through UseKey,
directly after it derives the key's mode and fingerprint and before any
goroutine starts; the Stripe integration's Startup no longer assigns it,
and nothing else does. UseKey assigns the trimmed key, so the SDK sends
the key whose mode and fingerprint boot derived, and an empty key leaves
the SDK's key as it was.
Every surface that asked a function for the key mode now reads the mode
boot derived: Checkout, the plan moves and the eager reconcile in
internal/server, the fulfillment entry points (SwitchPlan,
KeepCurrentPlan, CancelSubscription, the two reconciles and the
scheduled-change sweep), and the worker's billing activities. The Stripe
workflows' activities still read the SDK's key, which start set before
the worker existed. SDKKeyMode, Config.StripeKeyMode and
WorkerConfig.StripeKeyMode are gone. stripe-api-key is a secret, which
an operator override cannot replace, so the value boot reads is the one
Startup used to assign.
One behaviour changes until the next section lands: with no
temporal-host, the SDK now holds the key, so checkout sessions and plan
moves reach Stripe while the webhooks they cause are stored and never
processed. The next section refuses that configuration; the two are not
deployed apart.
The tests pass the boot mode, stripetest.KeyMode(), where they passed
the function. TestCheckoutThroughTheRouter lost its count of how often
the mode function was asked, since the function no longer exists, and
gained a row that posts a price mapped in live mode under the test key
and wants 422, which fails if the router hands Checkout an empty mode.
The eager reconcile's wiring is covered by no break.
Race baseline (go test -race -p 1 -count=1 over cmd, auth, server,
workflows and the three integrations, on
|
||
|
|
f9c9b3225e |
Run the console on a runtime user and migrate on a second connection string
Fourth section of #23. db-dsn now names the console's runtime user, and the new db-migrate-dsn (or db-migrate-dsn-file) names the user that runs migrations. start opens both, migrates on the migration connection, grants console_runtime to the runtime user there (WITH INHERIT TRUE, the name quoted by the database), closes it, and serves on the runtime pool alone. With db-migrate-dsn left out, start runs no migration and refuses when the schema is behind its code, which is how a host runs migrations as a separate step; migrate up, down and status use the migration string and refuse without it, with no fallback to db-dsn. Both pools are opened from one pgx.ParseConfig: the migration pool's search_path is public and both pools' role is none, set in the startup parameters after every key of either name is removed case-insensitively, so neither a setting the database or role carries nor a key the string repeats in another case moves a session off its login or its path. PendingMigrations reads each stream's ledger by qualified name inside a transaction pinned to search_path pg_catalog, pg_temp. The test stack runs on two users: postgres-seed creates member_console_runtime in new and existing volumes, bootstrap-stack.sh writes MC_DB_DSN as that user and MC_DB_MIGRATE_DSN as member_console, and the four scripts refuse a .env without the migration string, with the command that regenerates it. Scripts and fixtures that need a superuser on the application database (the reset, the screens' timestamp pinning) use the migration string. This worktree's stack was regenerated with that command and the console booted as member_console_runtime: its sessions were the runtime user's only, no migration-user session stayed open, and the runtime user could not insert into core.pool_provisions. Full suite on reset databases: 4277 pass, 15 skip, none failed. The walkthroughs' names, results and skip messages match the previous section's run. make screens lists three changed screens, all data: regenerated ids on operator-person-detail and operator-billing-invoice-detail (the demo snapshot was rebuilt) and a domain's age on operator-domains (294d to 295d); the baseline was not accepted. make lint passes. Deliberate breaks, scripts/mutation-check -side new, all CAUGHT (36): dbRuntime runs the migrations on the runtime pool dbRuntime skips the boot grant dbRuntime leaves the migration pool open dbRuntime skips the pending check when db-migrate-dsn is unset dbRuntime migrates on db-dsn when db-migrate-dsn is unset GrantRuntimeRole without WITH INHERIT TRUE GrantRuntimeRole with %s in place of %I PendingMigrations takes a missing ledger as current PendingMigrations resolves the ledger without the public. qualifier the migration pool opened without search_path=public the migration pool opened without role=none the runtime pool opened without role=none ConnectMigration keeps the search_path and role the string carries dbMigration falls back to db-dsn ValidateStart accepts a malformed db-migrate-dsn db-migrate-dsn-file is not a database secret pair db-migrate-dsn-file is not a database secret pair (the migrate commands read db-dsn-file only) the bootstrap guard removed reset-test-db.sh guard removed seed-demo.sh guard removed reset-app-db.sh guard after its first psql startup parameters match their keys case-sensitively the migration pool opened without role=none (keys and options in the string) the runtime pool opened without role=none (keys and options in the string) the runtime pool replaces the search_path the string carries the string's spaces are written as plus signs ValidateStart reads db-migrate-dsn as set whatever it holds PendingMigrations lists only the versions the ledger shows when it is missing migrationDSN keeps whitespace migrate up accepts a whitespace-only db-migrate-dsn runtimeDSN accepts a whitespace-only db-dsn openDB runs the migrating boot for an unknown mode bootstrap-stack.sh trusts the exported MC_DB_MIGRATE_DSN reset-test-db.sh trusts the exported MC_DB_MIGRATE_DSN reset-app-db.sh trusts the exported MC_DB_MIGRATE_DSN seed-demo.sh trusts the exported MC_DB_MIGRATE_DSN |
||
|
|
db1bf7c287 |
State each spec's purpose, and refuse the archive's placeholder
When `openspec archive` creates a spec, it writes "TBD - created by archiving change ..." as the spec's Purpose, and `openspec validate --strict` accepts it. Twenty-five specs still carried it. Each of the 25 now says what it defines, in one to three sentences drawn from its own requirements, and each was checked against them. The lint gains spec-purpose-placeholder: it reports a spec under openspec/specs/ whose Purpose is the placeholder or empty, at the placeholder's line, so an archive that creates a spec cannot land until its Purpose is written. `--specs-dir` points it elsewhere or turns it off. A table test covers the placeholder, an empty Purpose, one followed directly by a requirement heading, trailing spaces, Windows line endings, a written Purpose, a spec with no Purpose, and a placeholder outside the section. docs/testing.md lists the rule. |
||
|
|
2375d6ee29 |
Correct four statements in the testing and Go convention docs
The page anatomy rule list left out actions-column, which the lint emits. The fixtures paragraph said OrgType is the one raw INSERT, but Draft and Lifecycle also write a product's lifecycle status and Subscribe a subscription's commitment end, because no console act does either. The stack script paragraph said the scripts refuse arguments they do not take without saying which four. The read page bullet held up operatorNames as a name resolver without saying that a failed read still falls back to the caller's text for a missing name (#207). |
||
|
|
c8263bfe0e |
Turn on nilerr: a function that returns an error may not swallow one
docs/go-conventions.md says a failed read is never an answer, and nothing checked it. nilerr refuses one form of that: a function that returns an error catches one and returns nil. On the tree it found 15 such returns. 12 were the project lint's walkers, which the parent commit replaced. The other three: - CheckQuotaActivity answers a failed read of the sites entitlement as a quota of 0, which tells the member they have reached their site limit. That is #231; the return is pinned in .golangci.yml by its source line until #231 removes it. - paymentMethodID in the invoice reconcile returned no id when the mapped payment method id did not parse. The column is UUID NOT NULL, so that cannot happen with the data the schema allows; the function now returns the error, so a row that breaks the rule is reported instead of read as unlinked. Not probed: no test can store a value the column refuses. - The domain claim's connect probe answers a failed lookup of the operator's own target as ProbeStateError, the state that says the record could not be checked. That is the probe's answer, so the return is exempt with that reason. With the pins, golangci-lint reports none. 2 breaks, each caught by scripts/golangci-lint.sh: a read error returned as nil in the lint's readTree, and the payment method id's parse error returned as nil. The rule's page names nilerr as its check and says what it leaves to review: a failed read turned into an empty list or a zero by a function that returns a value. docs/testing.md lists nilerr among the linters. make lint passes; the Stripe workflows and lint packages pass with the database tests skipped (the test stack was down). |
||
|
|
061493920a |
Report a file the project lint cannot read instead of passing it
Six of the project lint's walkers (the page-template scan, the anatomy rules, the Go copy pass, retired resource keys, raw error rendering and walkthrough coverage) skipped a directory they could not list and a file they could not read, and discarded the walk's result, so a file the lint never read passed it. They now share readTree, which returns the files a rule keeps and reports a walk error as an unreadable-file violation naming the path. A directory that does not exist still holds nothing to read, as before; walkthrough coverage keeps its own check for a missing directory. pageBodyTemplates and eachGoString return the violations beside their results. TestReadTreeReportsWhatItCannotRead locks a file, locks a directory, walks a directory that does not exist, and runs ruleRawErrorRender over a locked file; it skips when run as root, which reads everything. 5 breaks, each caught (new side only; the test is new): a read error returned as nil, a listing error skipped, the missing-directory check removed, the violation filed under the walked directory instead of the path, and the raw-error rule dropping readTree's violations. On the working tree the project lint reports no unreadable file, the lint package's tests (the verdict golden included) pass unchanged, and make lint passes. docs/testing.md lists the new rule id. |
||
|
|
4dad0c48c7 |
Write down where a rule and an act live, output order, and how to prove a test
The code-organization rules the recent refactor worked to were written in no tracked doc. docs/go-conventions.md gains them: - "Where a rule and an act live": one function for a rule a page shows and an endpoint enforces (plans.Offer); one statement for a rule SQL and Go both need (core.superseded_ends); a many-row act in the package that owns its tables (entitlements.RemoveTier, ReorderTiers, PlanActError); a handler that parses, calls, maps and renders; a read page that loads, derives, builds and renders with one name resolver per request; and a failed read is never an answer. The handlers that still open a transaction or run SQL are #221, and the reads that still turn a failure into a value are #207 and #197. - "Output order": a list query ends on a unique key (#225 lists the ones that do not), a loop that writes rows walks a stated order (#218), and Go output from a map ranges over sorted keys. - No call to Stripe while a transaction is open. The transactions section now says new code uses the helpers and points to #186 for the BeginTx calls that remain. docs/operator-ux-conventions.md's failed-collection rule links the general one. docs/testing.md: - Characterize before a refactor, and what a refactor commit may change. - The deliberate-break rule covers new tests and tests whose names or comments state a value, names all six outcomes the break tool reports, and says how to read a MISSED break. - Tests of locks and of order are deterministic; a flaky test is a suspected defect; a database test passes twice without a reset and filters to its own rows. - Golden files: defined order, rows of the test's own fixture, an expected change edited in by hand before the run, `-update` one package at a time. - A helper joins the test kit with its first caller and a test. CONTRIBUTING.md: the Go and test conventions point to their pages; docs move with the code; a written rule names its check; a script that resets or deletes data refuses unknown arguments; "Before you push" runs `make test`, since without the stack the database and Stripe tests skip; a commit message reads from the repository alone; new source files carry the SPDX header; and a small fix rides along only when listed, while a second sighting, a new rule or lock, or two statements of one rule get their own issue. docs/README.md: one page per rule, and the Go conventions entry lists what the page now covers. |
||
|
|
925ed57ec6 |
Describe the test kit by what it covers, and point to go doc
docs/testing.md named world's builders one by one, and 21 of its 54 methods were missing from the list; dbtest, htmltest and the Stripe mock had drifted the same way. The fixtures paragraph now describes the builders by what they cover, and "The test kit" says that the page names the helpers a new test reaches for first while `go doc -all ./internal/testkit/<package>` (and ./internal/stripetest) lists every exported name with its comment, so the page no longer has to grow with each helper. |
||
|
|
e2f3b8aa44 |
Correct the docs that no longer match the code
docs/database-locks.md said recordObligationFailure records a failed drain in a transaction that takes the rendezvous alone. It runs MarkObligationFailed, which calls core.settle_obligation, and that function locks the obligation row and then the pool row, the reverse of the drain's order. The drain section now says so, and "Where the paths differ" lists it with #196; the two entries above it gain #193 and #194. docs/database-management.md described three schemas and three migration sources. There are five of each: domains and discourse were missing, with their ledgers and their place in the source order. Its sqlc example ran `sqlc generate` in one directory, which leaves the generated files without the SPDX headers `make sqlc-generate` restores; the example now uses the make target and says why. docs/testing.md: - One list of the project lint's rules, by rule id, under the static checks; the "Lint and screens" entry, which repeated part of it, points there. The TL;DR's `make lint` comment no longer lists four of its five checks. - The browser-test waits are described once, here; the copy in docs/go-conventions.md, which disagreed on which rod waits exist, is gone. rod's MustWait with a predicate, used by two walkthroughs, is named as allowed. - MockBackend's description names the fields tests use and the three ways it fails a call, in place of three of its twenty fields. - An example test that no longer exists is replaced by one that does, and a sentence fused with the next one in the screens entry is split. test/AGENTS.md: the suite takes a few minutes, not 26 seconds; the explicit-slug example runs from test/ like the rest of the page and states the slug's rule; the stack scripts' refusal of unknown arguments is described; and a link into one contributor's assistant memory is removed. The owner of a pinned lint exception or a test-only allowlist entry is an issue; the comments in .golangci.yml, scripts/deadcode-testonly.txt and docs/testing.md no longer offer a #166 step, since that issue is closed and no entry names one. golangci-lint, scripts/deadcode.sh and scripts/notebook-citations.sh pass. |
||
|
|
96ba5067a6 |
Simplify AGENTS.md by removing deprecated context-mode rules and
updating checks section language |
||
|
|
208e10dd9d |
Replay recorded claim verification and push histories through one replay kit
Add internal/testkit/replay: Run and File replay a package's recorded Temporal histories against the workflows it registers, and Record writes them with stand-in activities. TEMPORAL_REPLAY_RECORD names a temporal CLI binary (the recorder starts a private in-memory dev server from it) or a running server's host:port; TEMPORAL_REPLAY_ONLY, a file-name prefix, records the files it names again. FedWiki's replay test moves onto the kit with its fourteen histories and every subtest name unchanged. Its two variables FEDWIKI_REPLAY_RECORD and FEDWIKI_REPLAY_ONLY are replaced by the two above, and its recorder skip message now reads "TEMPORAL_REPLAY_RECORD not set". The recorder now writes only the histories that do not exist yet, where FedWiki's used to rewrite every file; a recorded history stands for runs a release started, so replacing one takes TEMPORAL_REPLAY_ONLY. Claim verification gets nine recorded histories (activates, polls, check-now, expires, probe-error, mark-refused, record-fails, canceled, rollover) and the push workflow seven (applied, rose, divergent, conflict-then-applied, permanent, unlinked, gone), each replayed by TestWorkflowHistoriesReplay in internal/workflows/domains and internal/workflows/desiredstate. The kit has its own test over a two-step workflow and a recorded history of it. The 17 histories were recorded at the parent's code, from a private dev server started from the temporal CLI (1.5.1); the commands they hold match an earlier independent recording of the same scenarios, apart from the two clamped timers' durations. No file names the recording machine or its user. New tests: the kit's TestTarget, TestRecordsNeverReplaceAHistoryUnasked, TestRunReplaysARecordedHistory (and its two-steps.json subtest), TestFileRefusesAChangedWorkflow, TestEndingNamesWhatTheRunDid and TestRecordTwoSteps (skips); the import guard's subtest for internal/testkit/replay; TestWorkflowHistoriesReplay with its 9 and 7 subtests and TestRecordReplayHistories (skips) in the two workflow packages. The kit is listed in docs/testing.md and in the test-only deadcode list. No output changes: no production file is edited. At the parent and at this commit, the names and results of the section's packages are equal apart from the tests above (FedWiki's 16 results pass or skip as before), the Temporal surface listings (activities, workflows, workflow bodies, payload types) are byte-equal, go run . lint prints the same line, the lint snapshot has no added or removed key, and deadcode and golangci-lint are clean. Breaks: 9 breaks, each caught by the new tests; the 2 FedWiki breaks (a request-driven create and a create started before lifecycle requests that check the quota first) are caught by the old and the new test. Not probed: Histories and Run failing a test (no test can watch its own test fail; every production break above shows Run failing a subtest); connect, runScenario and write against a server, which run only when recording (the recording at the parent is their check). A replay cannot see activity inputs, timer durations, the result, or the ending of a history that ends canceled or failed; later tests pin those. |
||
|
|
c20ab7dc3c |
Page the recent-activity feed so every event lists on exactly one page
The landing feed read the newest 100 rows of each of its four sources and
merged only those, so an event past a source's 100th row listed on no page,
the Showing line counted the window ("of 101" for 102 events), and a page
past the window read as empty. Page p of size s now reads each source to
depth p*s in the order the feed merges them in, cuts the page from the
merge, and sums each source's count(*) OVER () for the total. A page number
past the end shows page 1, as before; the depth is capped at what a query's
LIMIT takes, so act_page=2147483647 does not wrap.
The rows a supersession makes redundant are now named once, in the
database, and the lists' queries leave them out, so each count is the count
of the rows shown. Migration 00025 adds core.transition_change_kind (the
kind of change a transition records: a resumption, an extension, else its
type, moved whole from the Go classification), core.superseded_ends (an end
and the typed row of the same act that states it, with by_extension marking
an extension's transfer) and core.extension_handoffs (the grant an
extension's transfer handed a position to), plus an index on (pool_id,
effective_at), the pairing key. The organization page's Tier changes list
leaves out every superseded end; the feed leaves out the ends an extension
states and the extension's successor grant, now over the whole history
instead of the 100-row window. supersededEnds, foldSupersessions and
feedPairing are deleted, and the transitions are cut by (effective_at,
transition_id) like the other sources. Both views read the transition
reason, which the reader role withholds, so the reader-role column file
lists the six view columns as withheld; the role's script is unchanged.
Behaviour that changes: an event past a source's 100th row lists on its
page; the total counts every event; an extension older than the newest 100
tier changes lists as its tier change, not as its successor grant; and when
the transitions read fails, the extension's grant no longer stands in for
the tier change (it drops with the rest of that source, the warning
unchanged).
How it was shown to change only that: TestRecentActivityPagesEveryEventOnce
fails at the parent in its first two subtests (page 11 lists the payment
alone "of 101", and the extension's successor grant "of 102") and passes
here; its other two subtests pass at the parent and guard the hiding inside
the transitions query and the transitions' order. Names and results of
internal/server, internal/entitlements, internal/billing, internal/migrate
and internal/db are equal to the parent's (1778 result lines at the parent,
1787 here) apart from: new TestRecentActivityPagesEveryEventOnce (four
subtests), TestTransitionChangeKind and TestSupersededEnds (nine subtests);
deleted TestLandingFeedCutsEachSourceAt100Rows (its fact is the first new
subtest), TestFoldSupersessions (four subtests; its cases are the first,
second and fourth new TestSupersededEnds subtests, stated clause by clause
in SQL instead of kept as a Go oracle) and
TestTransitionChange_RecognisesMachineReasonsByWriter (its eight cases are
in TestTransitionChangeKind, with eight more); and the failure subtest "a
failed transitions read drops the tier changes, lists the extension's grant
and says so", renamed "... drops the tier changes and says so" and wanting
the issued grant, the payment and the invoice. Both goldens
(operator_activity and org_enrollment) are byte-equal, and the read-count
rows and TestRecentActivityReadsPerEvent are unchanged.
TestTransitionChange_OneLabelRule now takes (type, kind) pairs; the
classification half is in TestTransitionChangeKind.
Rewritten assertions that are NARROWER: the old test also read pages 1 and
2 of the 50-row size on the 101-invoice feed; the new one reads page 11 and
the clamped pages at 10 a page, so those two pages of a 50-row size are no
longer read (a 25-row size and page 2 of a 30-row feed stay in
TestLandingPagesTheActivityFeed). wholeFeed in the golden tests now asks
for the whole timeline on one page: a 400-event limit missed the fixtures'
billing events once other tests had written newer rows to the shared
database.
test/e2e/plan-management TestMVPPlanManagement step 8.6 read the transition
audit through the by-pool list and wanted 5 rows (initiate 2, upgrade 1,
end 2); the list now leaves out the end the trial grant's upgrade
supersedes, so it returned 4. The step now counts the 5 rows written in
core.pool_provision_transitions directly, then wants 4 listed (initiate 2,
upgrade 1, end 1). Wider: the audit fact is kept and the list is asserted
besides. The end-to-end package is outside this commit's checks; a copy of
steps 8.2 to 8.6 run on the test database passes here and, at the parent,
fails on the list's count (5 listed).
Checks: 25 breaks (12 in Go and generated code, 13 in the migration, each
migration break probed with the test databases reset before and after),
each caught by the new tests. The Down of 00025 ran once on a scratch
database (down one version, up again) without error. sqlc-unused reports
unused=0 and the deleted names occur nowhere in internal or docs. The lint
snapshot is unchanged (0 added, 0 removed). Walkthroughs are equal at the
parent and here (16 pass, 6 skip); screens at the parent and here show the
same changes against the baseline, all in the masked regions or equal on
both sides, so operator-overview is unchanged at both widths.
EXPLAIN (ANALYZE, BUFFERS) on the test database, with enable_seqscan on and
off: the restored-names subplan runs once per returned grant (10 loops),
the two anti-joins build the redundant set once, and the invoice and
payment reads scan each table once. A tiny database does not show how the
plans scale, so the same queries ran on a rolled-back fixture of 146,000
transitions: the by-pool list probes the primary key and the effective-time
index in 0.08 ms; the feed's transitions read, which counts the whole
history for its total, takes about 150 to 200 ms there; with tier changes
sharing a few instants, the pairing probe uses the new (pool_id,
effective_at) index.
Unprobed: the by-pool list's pairing probe chose the pool_id index with an
effective_at filter instead of the new index (a pool's rows are few, so the
choice costs nothing; the new index is used where one instant spans many
pools); the statement count of a page after the first (the read-count rows
read page 1, and a later page runs the same four statements); the total
when a source read fails (the failure table asserts rows and warnings, not
the total); a grant-less provision or a provision-less extension in
core.extension_handoffs (no writer records one); the legacy transition type
'extend' in the views (no writer has written it since an early migration's
type check left it out; the function's test pins its kind); and a source
whose rows and count come from two snapshots, when an event is written
between two of the four statements.
|
||
|
|
0ce17b598e |
Characterize the sweep per kind of due row, on a scratch database
TestSweepTrace runs SweepDueScheduledChanges on 12 kinds of due row and writes one golden file per case under internal/fulfillment/testdata/sweep_trace, in the format of the reconcile goldens: the count and error the sweep returns, its statements in order with each Stripe call placed among them, its log lines, and the rows afterwards. The cases are a due period-end cancellation, a cancellation Stripe refuses, a period-end downgrade a day late, a period-end downgrade whose switch Stripe already made, a plan switch deferred to the term boundary and the same switch Stripe refuses, a deferred switch whose target price is recorded in live mode, one whose item is, a cancellation whose second row finds its claim gone, a quantity change, and a cancellation on a subscription already marked canceled, and one on a subscription whose ended_at is set with its status left active. Each case gets its own scratch database, migrated with the core and stripe streams (the stripe stream needs no stream before it), because the sweep fires every due row of its database and on the shared database the count and the trace would depend on what other tests left due. newTraceEnv takes a scratch parameter for this; the two earlier trace tests pass false. The helpers due and sweep land with their first caller here. No production file changes and no output changes. The set of test and subtest names of internal/fulfillment is the parent's 110 plus TestSweepTrace and its 12 subtests (123 names, all passing on both sides, each side run after a reset of the test database). Each golden was read against the fixture and the code before it was committed. The new test and the two earlier trace tests pass three runs in a row, a run without -update leaves the tree unchanged, and the 29 earlier goldens are unchanged. docs/testing.md says the sweep's cases run on a scratch database. 17 breaks of sweep.go, each caught by the new test (a new test has no old side). Defects the goldens pin: - #177: the sweep claims a row before it calls Stripe, outside the transaction that writes the subscription's change. A refused deferred switch leaves the row applied with nothing done and counts nothing; a refused cancellation leaves it applied and counts one; a quantity change is claimed with nothing done. - A due period-end cancellation is cancelled at Stripe without asking whether Stripe still cancels at the period end. - The count is every nil return, so skips, lost claims and quantity changes count, against the function's comment "the number applied". - A deferred switch moves the oldest current item, not the ladder's item; the case is built so the two agree. Unprobed, with the reason: - the three firing errors that print %!w(<nil>) (a mapping without a Stripe id, a subscription without a current item): no console path leaves either while a row is due; - a failed due list or a failed claim other than no rows: both need a failing database; - the plan switch without a target price: no console path writes one. |
||
|
|
9df43c1489 |
Characterize the subscription reconcile per status
TestReconcileTrace runs 21 cases of ReconcileSubscription, each on a fresh fixture, and writes one golden file per case under internal/fulfillment/testdata/reconcile_trace. A golden holds the act's result, the statements it ran in order with the begin, commit or rollback and each Stripe call placed among them, the log lines, and the rows the fixture holds afterwards (subscription and its changes, items, scheduled changes, provisions, ladder positions, transitions and the entitlements). The cases cover first sight, replay, a quantity change and a quantity of zero, a price swapped in place, an item replaced by another and an item without an id, an add-on added and removed, a product without an entitlement set, past due and active again, incomplete, the canceled branch (back to the default plan, replayed, and resuming a held grant), a refused fetch, a subscription without a customer, an unmapped price, and a mapping recorded in the other key mode. The trace helpers sit in trace_db_test.go: the code under test runs on a counted handle, fixtures on another, and an unnamed id left in a golden fails the test instead of making it flaky. A materialization is folded into one line (its statements are pinned by the materialize golden) and each run of conferral writes is sorted, because the reconcile ranges over a map there. docs/testing.md gains one bullet. The golden of grant_over_subscription pins a defect (#212): a reconcile confers the subscription's tier again over the operator's grant that replaced it, ending the grant. The three canceled cases (canceled_to_default, canceled_replay and canceled_resumes_grant) pin the order the ended branch runs its steps in as it is today (#180). The 14 goldens that show the status sync pin it running with no pool lock taken before it (#193). A fix to any of the three flips those goldens on purpose. No production file changes and no output changes. The set of test and subtest names of internal/fulfillment is the parent's 79 plus TestReconcileTrace and its 21 subtests (101 names, all passing; the parent's all pass too). Each golden was read against the fixture and the code before it was committed; a second run without -update leaves the tree unchanged and the test passes three runs in a row on a reset database. Where the Stripe fetch is concerned, the goldens print the SDK's own encoding of the expand parameters, expand[0] and expand[1]. 29 breaks of reconcile.go, each caught by the new test (a new test has no old side). Unprobed, with the reason: - the error texts of the reconcile other than the three the cases pin: each sits on a failed statement that no fixture causes without a failing database; - that the mapping read before the transaction runs on the pool and not in the transaction: the trace shows it before the begin, not which connection ran it; a failed read other than no rows is ignored today and cannot be caused; - the order among statements of one name issued per row of a read ordered by creation time alone (ending an item, ending a surplus conferral): the trace shows their count, not which row went first; - the order of the desired loop, sorted on purpose because Go's map order varies; - materialization's own statements, folded (the rows show what it writes); - two reconciles of one subscription at once: no case runs them; the lock statement is in every trace that reaches the transaction (all but fetch_refused and unreachable), but a trace cannot show it held; - a quantity change moving the entitlement limit: the fixture's limit rule is not per unit, so the quantity golden shows the item and the provision at 3 and the limit at 1; - ReconcileLatestSubscriptionForCustomer, unchanged and pinned by its own test. |
||
|
|
6f65c744fe |
Pin how the plan-wide acts interleave with each other, with a rule change and with a conferral
The plan-wide acts (tier removal, default change, reorder, tier add and delete) are now sent against each other and against a conferral, each interleaving chosen by a lock a side transaction holds and releases once the requests wait on it. dbtest.AwaitLockWait proves a request is queued, and PostgreSQL grants a row lock to its waiters in the order they queued, so no test sleeps. The kit gains Backend.Waiters, how many backends must wait at once, with a test of its own; its first caller is these tests. TestTierRemovalAgainstDefaultChange runs a removal and a default change on one pool in each order: queued on the pool's row, and one after the other. Every case ends with no live position on a deleted tier. TestDefaultChangeTwiceSerializesOnThePool sends one default change twice and shows one request grandfathering the pool and the other finding the legacy grant the first wrote. TestPlanActsRefuseWhileARuleChangeHoldsTheRendezvous holds the exclusive rendezvous while all five acts run, and pins each act's contention answer: the removal's and the organization's "Another entitlement set change is in progress. Try again." and the generic sentence of the reorder, the add and the delete. It waits out the 5 s lock timeout once. TestRenumberingCommitsDuringAConferral commits a reorder, a tier delete and a tier removal while a default change's conferral waits between its rank reads; the files pin the transition as it is recorded today (upgrade 0 to 1 for the reorder where a transfer belongs; downgrade 1 to 0 for the delete and the removal), because a renumbering takes the rendezvous in shared mode only. Pinned with #187: TestReorderEnactsAgainstTheRankZeroItSaved sends two reorders whose enactments queue on one pool; the first enacts against the rank 0 it saved after the second has put the other product back at rank 0, so it grandfathers the holder onto a legacy grant of the product that is rank 0 again, and the second finds the holder on that grant and leaves it. No output changed: the commit adds tests, their golden files and one field of the test kit, and touches no production file. The names and results of ./internal/server and ./internal/testkit/..., run after a database reset at the parent and at this commit, differ by the 13 new tests and subtests, all passing; the new tests passed three runs in a row and 20 runs of the four that do not wait out the timeout. The edit to the plan-wide acts' golden test is its JSON encoder, split so the new tests encode their files the same way; its 44 files are unchanged. The kit's wait.go and wait_test.go and the AwaitLockWait bullet of docs/testing.md change beside it. 14 breaks, each caught by its new test (new side only): 12 edit production code unchanged from the parent and 2 edit this commit's wait.go. The section's tests take about 6 s, 5 s of it the one wait. Unprobed: - Interleavings the tests do not choose, such as a removal that arrives between the default change's save and its pool lock; the cases cover the two queue orders on the pool and the two serial orders. - A conferral other than the default change's during a renumbering (a reconcile, a grant act, the floor); it is the same database function, which the reorder case reaches. - The default change's own stale candidate: a renumbering between its save and its enactment. It uses the same fixed product the reorder case reaches; no case pauses the default change there. - The two new failure messages of AwaitLockWait for a count above one; they are text, read only when a wait fails. |
||
|
|
25fb6e45af |
Build product mappings and a product without its set through the kit
world gains MapProduct and MapProductIn, which give a product a synced Stripe mapping through the console's own upsert (Stripe id prod_<product id>, no environment or the one asked for), and WithoutSet, which clears a product's entitlement set through EditProduct the way the operator's edit form saves a product with no set chosen. TestCatalogBuilders reads both back through the console's queries. No builder writes a dead-lettered outbox entry; a test that needs one makes it through the console's MarkOutboxDeadLetter. No production file changes and no existing builder writes anything new. Names and results over ./internal/testkit/..., internal/entitlements, internal/fulfillment, internal/server, internal/integrations/discourse/web, internal/integrations/fedwiki/web and internal/integrations/stripe/workflows, taken with the test database reset before each side, are equal at the parent and here except the new test and its two subtests, which pass. The package test/e2e/plan-management, which also builds through world, compiles under go vet; it calls none of the new builders and no existing builder changed. 5 breaks, each caught by the new test; none dropped. Unprobed: MapProductIn(p, false), which is the same statement with the flag false; the test reads the live case. |
||
|
|
ae84dc569d |
Give tests the Stripe SDK key's mode as the console's configuration gives it
stripetest.KeyMode returns the mode of the key the Stripe SDK holds when it is called: "test" once Install has set its key, "" when the SDK holds no key, "live" under a live key. A test passes it where the console passes its configured key mode, and it reads the key at the same moment production does, so a test that never installs the mock keeps the mode it has today. The docs say so beside the other stripetest helpers. No production file changes. The package's tests run with the app down on freshly reset test databases at the parent and at this commit; the names and results are equal apart from the one new test, which passes. One break (reading a fixed key instead of the SDK's key) is caught by the new test; the test has four cases, none unprobed. |
||
|
|
a36003910f |
Note each Stripe call among the statements around it
A test can now put a line of its own among the statements a Counted handle recorded, and have the Stripe mock hand it every call it receives, so one list shows each Stripe call between the statements it ran between, begin and commit included. Statements.Note places a line after the statements recorded so far. Names returns statements and notes in order; Total and ByName leave notes out; Reset forgets them. MockBackend.OnCall is called for every request as soon as it is recorded, before Err, PathErrs or Handle answers it, so a refused request is seen too. The recording moved from Call into a small record method, which keeps (*MockBackend).Call at its cognitive complexity of 65. stripetest.Line prints a call as "stripe <METHOD> <path>" and its form's pairs sorted by key, unescaped. docs/testing.md names OnCall, Line and Note. No production code changes, so no output changes: only test helper packages and docs/testing.md are edited, and the build, vet, gofmt, lint and deadcode checks pass with the lint snapshot unchanged. The names and results of ./internal/testkit/..., ./internal/stripetest, ./internal/fulfillment, ./internal/integrations/stripe/... and ./internal/server equal the parent's (1767 at the parent, 1771 here; every result pass), the four additions being the new tests TestLineNamesTheCallAndItsSortedForm, TestOnCallSeesEveryCallInOrder, TestOnCallSeesARequestErrRefuses and TestNotesTakeTheirPlaceAmongTheStatements, after a database reset before each side. Eleven breaks, each caught by the new test. Not probed: OnCall called from concurrent goroutines. The mock appends under its mutex and calls OnCall outside it, and no test of this series calls Stripe from two goroutines at once. |
||
|
|
ff04f2ee86 |
Print a query's rows as text for a golden file
dbtest.Q.Rows runs a query and returns a line of column names, then one line per row in the query's order, values separated by " | ". NULL reads NULL, a time reads in UTC as RFC 3339 with its fraction, bytes read as text, anything else as fmt prints it. The query names its own order. docs/testing.md lists Rows with the Q methods. No production file changes. Shown to change no output: the tests of ./internal/testkit/... listed by name and result before and after differ by the one new test, TestRowsPrintsColumnsThenRowsInQueryOrder, and every test passes; the kit's imports guard still passes, and the lint snapshot has no added or removed line. 5 breaks, each caught by the new test (a new test is proven on the new side only); none dropped. Unprobed: how the driver hands over each column type (uuid, numeric and json arrive as text or bytes, both printed as text; which one a type takes is the driver's choice, so the test does not name it), and the error branches (each fails the test with the whole query folded onto one line, as Strings does). |
||
|
|
8cee42f2d3 |
Compare golden files and name a run's ids through one kit package
The server's golden helpers (the comparer, the -update flag and the normaliser that turns a fixture's ids into names) move into internal/testkit/golden, so any package's tests can compare output with a file under testdata. Ordinals is new: it numbers the UUIDs and UTC times an act makes by first appearance, for the ids a fixture cannot name. Normalizer.Add replaces the one place a server test appended to the normaliser's unexported pairs. Eleven server test files use the package; the -update flag keeps its name. docs/testing.md gains the kit row and a bullet, and the kit's import guard and the dead-code list know the package. No output changes. No production file changes, and no file under internal/server/testdata is touched; every server golden test passes without -update and git status is clean under testdata afterwards. The names and results of ./internal/testkit/... and ./internal/server are equal at the parent and at this commit (1452 and 1458 tests and subtests), apart from the five new tests and the import guard's new subtest for the package. No assertion changes, so no assertion map. 13 breaks: 12 caught by the new tests, and the one that renames the normaliser's <today> pair is caught by the golden test at the parent and at this commit. Unprobed: that a name outranks a pair in the server's goldens (their ids are UUIDs and their pairs a date and the suffix, which never start at one position, so the order is invisible there; the kit test probes it); the server copy of the comparer at the parent (a break that makes it pass everything cannot fail the goldens it compares); a migration test that defines its own update flag (a documented rule, not code). |
||
|
|
dd38fce93a |
Name the statements a Counted handle ran, in the order they ran
Statements.Names returns a copy of the recorded names in run order. A transaction's begin, commit and rollback are among them, because pgx sends them as statements the tracer sees, so a test can read where each statement fell against the transactions around it. docs/testing.md lists it beside ByName, Total and Reset. No production file changes. The test packages under internal/testkit run with the same test and subtest names and results at the parent and at this commit, apart from the new TestCountedNamesStatementsInOrder (37 results before, 38 after, each run after a reset of the test databases). 4 breaks, each caught by the new test: sorted order, a dropped first name, the shared slice returned instead of a copy, and Reset leaving the names. Unprobed: the mutex, because every caller records on one goroutine. That rollback is recorded is pgx's behaviour; the test asserts it, but no one-line edit of the kit removes it, so no break covers it. |
||
|
|
eb4a7552bd |
Pin every route's pattern and answer through the router
No production file changes. authtest gains Cookie, which commits a session and returns its cookie, for a handler wrapped in the session middleware, such as the whole router, where Get and Post cannot put the session on the request's context. docs/testing.md names it and the kit table's authtest row says "signed-in sessions, their cookies, and in-process requests". internal/server/new_handler_test.go (no database) builds the router from a configured profile and a bare one (no Stripe key), with a fake route mount and two UI mounts, and adds five tests. TestNewHandlerRouteTable writes one block per registered pattern (the 97 literal HandleFunc patterns the lint scan finds in internal/server and internal/auth, plus the two static-file Handle registrations and the fake mount's three routes) to testdata/routes.golden: the pattern the mux resolves a probe path to, and the answer to an anonymous request, a cross-site request for a method that changes state, and a signed-in member where the handler answers before any read; then eight extra probes and the bare profile's checkout row. The golden was written once with -update and read against the code: every operator row answers 302 /login anonymously and 403 Forbidden to the member, the cross-site writes answer the CSRF refusal except the fake webhook (204), the two wrong methods and the bare checkout resolve to / and the styled 404, and the body one byte over 1 MiB answers 413. TestNewHandlerTrustsTheBaseURLOrigin, TestNewHandlerRefusedSignInIsStyled, TestNewHandlerRefusesAMountTemplateOutsideItsKey and TestNewHandlerPassesDepsToMounts cover the base URL's origin, the styled refused sign-in, the key-prefix refusal and the Deps a mount is handed. Shown to change no output: no production file is touched, and the names and results of internal/server and internal/testkit equal the parent's apart from the five new tests, which pass three runs in a row. The 47 breaks of the plan, each applied to the new side after a green control run, are all caught. Left unprobed: - The answers the golden records that code outside the router produces (each handler's own answer, the auth handlers, the operator role guard, each middleware's behaviour): each has its own tests, and no edit to the router changes them; the golden holds them so a change shows. - middleware.Timeout(32s): a short value races fast handlers, and a probe of the real value waits 32 seconds. - middleware.Logging and the order of RequestID before it: they write a log line no test reads. - The http.Server fields: Start keeps them, and nothing builds the server in a test. - The dashboard's root-only check: a later commit probes it. - The real integrations' routes (FedWiki, the Stripe webhook, Discourse): they mount from cmd/start.go, and the fake mount stands in. - The Config cmd/start.go builds (including StripeKeyMode and BaseURL) and the Start call with its exit path: no test builds the command's Config; these tests build their own. |
||
|
|
bf6ad44a7c |
Characterize the catalog's other per-row reads: set history, sets, the setup step and product prices
Four new tests pin what four catalog loaders show when a read that names or counts per row succeeds, fails or has no querier: the actor each change of a set's history names (a person's change names the person, the system's names nobody, a failed person read and a missing identity querier name nobody), the active rules each set in the sets list counts (a failed read counts 0), the first unconfigured provider the setup checklist's integrations step names in set order (a failed rule read skips every set), and the Stripe mapping each price on a product shows (a failed read or no Stripe leaves every price not mapped). Four rows join the read-count table, each measured at one statement per row, as predicted: entitlement-set-history, entitlement-sets, setup and product-prices. The table's page type gains the integration configs the setup row needs. world gains RuleChange, a person's rule-added commit through the batch commit, which settles the carrying pools; Rule stays the system's unsettled path. registerGoldenKeys takes a testing.TB so a read-count build can call it. No production file changes, so no output changes: the goldens are untouched, and over ./internal/server and ./internal/testkit/... the set of test and subtest names and each result equals the parent's apart from the 18 new names (4 tests, 10 subtests, 4 read-count subtests), none missing, none changed, no failures; three runs in a row on a reset database are green. 19 breaks, each caught by the new test; the old side does not exist for a test that is new. Unprobed, since no section of this step moves their code: the history's other columns and paging, the sets list's failed-obligation count, the setup page's other steps, the price rows' amounts and intervals. |
||
|
|
2a913ca635 |
Characterize the plan-ladders page, the ladder page's tiers and the reorder preview
Adds tests, one test-kit builder and goldens only; no production file changes. TestPlanLaddersGolden builds three ladders (Hosting with Basic and Shared, Support with Shared, Empty with no tier), five products (a shared one, an add-on, one with no category, a draft), three organization types (defaulting to Hosting, to nothing and to the empty ladder) and an organization holding Basic, and compares with files under testdata/operator_plan_ladders: the topology's columns, rank rows, shared products, off-ladder strip and type summary together with the ladder list (page); the ladder page's tiers with their organization counts (detail); and the reorder preview's order for the ladder's own order (preview). The page lists every ladder in the database, so the files hold the fixture's ladders, products and types. The properties subtest holds the whole grid to the ladders' own reads: rows ascend by rank, every row has one cell per column, and every column, the fixture's or not, counts what CountActiveAttachmentsByLadder returns and holds the tiers ListTiersByLadderWithProducts returns. Each file was read against the fixture before it was committed; a type whose default ladder has no rank-0 tier reads as no default. TestPlanLaddersReadFailures pins, per read (ladder listing, shared products, ladder counts, tier reads, product list, rank-0 listing, organization types, tier counts on the ladder page and in the preview), what the pages show when that read fails. world gains AddTier. The read-count table gains plan-ladders (4 reads per ladder), plan-ladder-detail (1 per tier) and tier-reorder-preview (2 per tier), each as the plan predicted; the table's request can now be a POST. Names and results of ./internal/server and ./internal/testkit/... match the parent apart from the new tests, which pass: TestPlanLaddersGolden and its four subtests, TestPlanLaddersReadFailures and its nine, and the three read-count rows. A second run without -update leaves the tree unchanged, and the tests pass three runs in a row after a reset. Thirty-two edits to the topology and the ladder code, each breaking one fact, fail the test that names it. Unprobed: each column's and list row's IsActive and description, the org-type summary's display names (moved unchanged); the list's name filter and paging, which no section of this step edits; the order of same-named products in the shared index, which is unspecified today and which no section of this step fixes; the reorder preview's classification path (rank 0 does not change in the fixture); the topology's template rendering, which its own tests keep. |
||
|
|
0fe2305905 |
Characterize the products list: a golden of its rows and a table of its failed reads
Adds tests, two test-kit builders and goldens only; no production file changes. TestProductsListGolden builds thirteen products (a ladder tier, an off-ladder product, an add-on, one with no set, one with no rules, one priced in the other environment, an unlisted, an inactive, a draft, a retired, one needing an unconfigured provider, and two sharing a set) and compares the rows the loader builds, and the page rendered from them, with files under testdata/operator_products, with Stripe configured and not. Each file was read against the fixture before it was committed, and each failure file against the fallback that read gives every row when it fails. The golden files show the verdict, note, set name, category and visibility of every row, the provider leg naming the unconfigured provider, and both set cells naming the shared set. TestProductsListReadFailures pins, per read of the page (shapes, set names, prices, price mappings, set rules, resource keys, and the page itself), the rows the loader builds when that read fails, and that a failed page read says "Failed to load products" and builds no row. world gains EditProduct and Lifecycle. The read-count table already holds the products row at 0 per row. Names and results of ./internal/server and ./internal/testkit/... match the parent apart from the new tests, which pass: TestProductsListGolden and its two subtests, TestProductsListReadFailures and its seven. A second run without -update leaves the tree unchanged, and the tests pass three runs in a row after a reset. Nineteen edits to the list code, its readiness helper and its template, each breaking one fact, fail the test that names it. One of them (the price reader told "configured" when Stripe is not) changes no output in that state, because the mapping read is skipped there and the map stays empty; it is run in the configured state with "not configured" instead. Unprobed: the view model's ProductID, Name, Description, IsActive, LifecycleStatus, Lifecycle and EntitlementSetID fields as individual facts (the golden compares them, and the other breaks show it compares every field); the skipped mapping read when Stripe is not configured, since no output depends on it; the order of the six reads; the row order, which the list query fixes; that two products sharing a set pass its id to the set and rule reads once (the ids a batch read receives change no output, and the page reads the sets in one statement either way). |
||
|
|
f53186bb6b |
Characterize the four billing lists and the lookup's candidates, with their read failures and reads per row
Adds tests, three test-kit builders and goldens only; no production file changes. TestBillingListsGolden compares each list's data, in the default and the all state, with a file under testdata/operator_billing for a fixture of two organizations and four billing accounts (live, unrecorded, unmapped, test), each with a subscription, an invoice and a payment. Each file was read against the fixture before it was committed: the test-mapped row is hidden by default with the line counting one, shown with the test badge in the all state, the unmapped row reads not_mapped, and every row names its own organization. TestBillingListsKeepTheirFallbacksWhenAReadFails pins, per list, that a failed organization read leaves the name empty and a failed mapping read shows not mapped. TestLookupInvoiceCandidatesNameTheirOrganizations pins the invoice candidates' organization names and the organization id as their fallback. The read-count table gains billing accounts, billing subscriptions, billing invoices, billing payments and lookup rows. Each grow adds its rows on a new organization named from a counter, and the lookup row is sent as an htmx request. Measured per row: 2 for each billing list, 1 for the lookup, as predicted. world gains MapAccount, MapSubscription and MapPayment, which record no environment, beside MapInvoice. Names and results of ./internal/server and ./internal/testkit/... match the parent apart from the new tests, which pass: TestBillingListsGolden and its twelve subtests, TestBillingListsKeepTheirFallbacksWhenAReadFails and its twelve, TestLookupInvoiceCandidatesNameTheirOrganizations and its two, and the read-count subtests billing_accounts, billing_subscriptions, billing_invoices, billing_payments and lookup. A second run without -update leaves the tree unchanged, and the tests pass three runs in a row after a reset. Thirty-two edits to the list code and the lookup, each breaking one fact, fail the test that names it. Unprobed: the goldens pin the loaders' other fields (dates, amounts, statuses, account names, payment method, invoice numbers, Overdue, the pager and the absence line) with no break, because no later section edits the code that fills them; the breaks probe the fields the mapping and naming changes move. |
||
|
|
6e7d0aea47 |
Drive the billing environment filter through signed-in requests on the kit
TestBillingViewsFilterToTheKeyEnvironment now builds its rows through the world kit (organization type, owner, organization, billing account, invoices, payments, bare subscriptions and their Stripe mappings) and drives the operator routes with a signed-in operator through authtest. Each subtest is a method of billingViewsFixture and reads the parsed page (the absence line, its switch, the invoice rows, the facet pills) instead of searching the body for strings. The gocognit exception for the test is deleted; its largest function now scores 10. The kit gains BareSubscription (a subscription row and nothing else) and the mapping builders MapInvoice, MapInvoiceIn, MapPaymentIn, MapSubscriptionIn and MapAccountIn. seedPayment, seedSubscription and seedAccountMapping are deleted with their last caller, with the unused member session; seedInvoice stops returning the id. No production code changes. Shown to change no output: names and results of ./internal/server and ./internal/testkit/... are identical at the parent and at this commit (1204 tests each, none failing). 17 breaks, each caught by the new test; 15 of them also caught by the old test. The payments and subscriptions noun breaks are new side only, because the old check could never fail for them. Assertion map rows: every row same or wider, none narrower. One general weakening: the badge's exact markup (`>Test</span>`) is now read as whitespace-collapsed text. Fixture differences: names and invoice numbers carry the world suffix instead of the be<UnixNano> marker; the organization type comes from OrgType; the organization has a default pool; the owner is a world person; the signed-in operator is a person of its own. The parent and commit database dumps agree apart from these differences. Unprobed: the absence line's count, which TestAbsenceLineCountsUnderTheSearch pins; the LIVE and NULL rows' lack of a badge in the all state, not asserted before and not added. |
||
|
|
df3de19115 |
Check the operator invoice detail through the kit and the HTML query helper
TestGetOperatorInvoiceDetailPage builds its organization, billing account, price, invoice and line through the test kit's world on the shared database instead of raw INSERTs in a rolled-back transaction, signs in an operator through authtest, and reads the page through htmltest. The kit gains LineItem, which writes a line the way the invoice reconcile does. The gocognit exception for the old function leaves .golangci.yml; the largest new function is well under the limit. No production file changes, so no output changes. The test and subtest names are the same; the full result sets of ./internal/server and ./internal/testkit/... (1204 tests) are identical at the parent and here. Eighteen breaks of the production code the page runs through were each caught by the new test, and the seventeen that the old test can see were each caught by the old test too; the Overdue badge's danger tone is proven on the new test alone, because the old check (text-bg-danger anywhere in the body) could never fail. Assertion map: every old check is the same or wider, with no narrower row. One general weakening, for markup that states no fact: the title's and the id's utility classes are no longer asserted. The amount check reads the "Amount due" cell alone; the old check passed on that cell or the line's amount, so the line's own amount stays unasserted, as before. The old retired-markup absences stay raw over the whole body. Fixtures differ in ways no assertion reads: names carry the World's suffix; the organization has a default pool; the owner is a world person and a second person, the operator, is signed in; the product is published with an active set; the price is the product's default, as CreatePrice makes a product's first price; the rows commit and the truncate is gone. The parent and commit database dumps agree apart from these differences (the parent's taken before the 404 subtest, because reading the non-UUID id aborts the old transaction). Unprobed: the line's own amount, which neither test asserts alone. |
||
|
|
74000dae68 |
Build billing rows and grant, tier and subscription acts through the kit
Add world builders that write through the queries and functions the console calls: BillingAccount, Invoice and Payment, IssueOpts.Note, Extend (the Manage act's extension), ReorderTiers, RemoveTier and EndSubscription (the reconcile's terminal branch). Subscribe now remembers the pool it conferred on. No existing builder writes anything new: IssueOpts.Note is empty by default and is then written as NULL, as before. Shown to change no output: only test-kit code and docs change; no production file is touched. The tests of internal/testkit/..., internal/server, internal/fulfillment and both integration web packages pass on a reset database with the same names and results, and the lint snapshot is identical to the parent's. The two new tests (TestBillingBuilders, TestActBuilders) were checked with 15 breaks, each caught; none dropped. Each TestActBuilders subtest builds its own World, and three more breaks (the floor, a subtest's failure charged to the subtest, both payments read back) were each caught. Unprobed: RemoveTier's refusal of a held tier (a Fatalf inside the builder), EndSubscription's closing of scheduled changes (no fixture schedules one on a subscription that ends), and an invoice's due date, period and paid time (written as given). |
||
|
|
2790c6bbc7 |
Count the statements a request runs, and pin the products page at none per row
dbtest.Counted opens the test database on a handle that records the name of every statement run on it (a generated query by its query name, anything else by its first line), with Reset, Total, ByName and Diff. A new table test, TestOperatorPagesReadAFixedNumberOfTimes, grows a page by one row, then by two more, and asserts that the second request runs 2*perRow more statements than the first. Its first row is the products page at 0 per row, which holds today. No production file changes, so no output changes. Tests of internal/testkit/... and internal/server pass on a reset database, and the read-count test passes three runs in a row. New tests: TestCountedRecordsEachStatementByName and TestDiffListsTheNamesWhoseCountsDiffer (dbtest), and TestOperatorPagesReadAFixedNumberOfTimes with its products subtest (server); every other name and result equals the parent's. 8 breaks, each caught by the read-count test (new side only). Unprobed: the mutex in Statements (every handler under test runs its statements on the request's goroutine, so nothing records concurrently); pings and prepares going unrecorded (a ping happens only after a second of idleness, and nothing in the module prepares a statement); Counted failing on a database behind the migrations (ledgerBehind, which the Open test pins). |
||
|
|
c77c9d7300 |
Correct the documents that name moved code
The storefront and consumers rows name the files that now hold the plan, add-on and entitlement code; the integration guide drops member_products.go from the files that stay in core and says the files of the split import no Stripe package; the resource-pools dimension states what the schema admits and what the console refuses; the plan ladders card gains a Member position row; the kit table says the fixture builder covers plan fixtures. No output changes: only Markdown under docs/ is touched, no Go, SQL or template file. git grep for member_products.go in docs and scripts is empty, make lint and the notebook-citation check pass, and the lint snapshot differs from the previous one by removed 0, added 0. |
||
|
|
219f734e9a |
Document the test kit, the transaction runner and the surface constructors
docs/testing.md: the front-matter summary names the test kit; the TL;DR runs make test, and one package on the same footing after a reset. Database-gated tests open through dbtest: Open skips when TEST_DATABASE_URL is unset, never migrates and refuses a database whose core ledger is behind; OpenE2E and DSN; Scratch, UpTo and ApplyVersion for migration subjects; Q and AwaitLockWait. The self-migrating table and the hand-list warning are gone. The fixtures paragraph describes world (console functions, fails on any error, suffixed names, no cleanup, any *sql.DB) and states the exemption for a test of the schema itself. Type 4 names the server harness's set helpers (OperatorSet, MemberSet, Render), htmltest selectors by role and by aria-*, hx-* and data-* attributes, and authtest. Type 7 gives browsertest's rules: boolean waits only, ClickAndLand for a navigating answer, AwaitSwap for a swap, Recover in each walkthrough, AppDB never migrates and skips without its key, free functions taking t, and phases as named functions. A table lists the kit's packages and what each may import. A paragraph describes proving a converted assertion with a deliberate break and scripts/mutation-check. The complexity bullet says a closure passed to t.Run counts toward the enclosing function. The lint bullets say the lint parses each template in the set its surface builds in production and fails on a file no surface parses, and that forbidigo refuses rod's load and stable waits. The stale internal/workflows/stripe path becomes internal/integrations/stripe/workflows, and the notes that described TEST_DATABASE_URL as MC_DB_DSN, the shared-database fixture pollution and the source-order convention now describe the separate test databases and the reset. docs/go-conventions.md: a transaction opens through db.InTx or db.Begin with a kind (entitlements.Materializing or RuleChange for rendezvous transactions), its begin and commit errors unwrapped; a pool lock goes through LockPools or LockPoolOfGrant, and the order lives in database-locks.md; test helpers take testing.TB and fail on error; "Where the checks live" names the rod wait rule, the waits it refuses and those that pass it. docs/database-locks.md: the pool row's "Taken by" names LockPools and LockPoolOfGrant and their callers; Revoke, Manage, tier removal, default change and the rule change say LockPools; the intro of the order section names the two kinds as what sets lock_timeout, says a plain transaction sets none, and says which paths render the lock refusal; rule 3 says "through LockPools"; the #186 bullet keeps only the advisory-key half. test/AGENTS.md and test/reset-test-db.sh: the reset is the only migrator and dbtest.Open refuses a stale ledger; the walkthroughs run before and after a change from the demo snapshot. docs/building-an-integration.md, docs/design-system.md and docs/operator-ux-conventions.md: template sets come from web.MemberTemplates and web.OperatorTemplates with the integration's web.Mount; operator files are named <key>_operator*.html; no ParseUIPartials and no per-set surfaceRoot or routeURL registration; the helpIcon row names forms.Help and forms.HelpIcon. The DB-backed tests bullet of docs/building-an-integration.md says integration tests open through dbtest.Open, the reset migrates every registered stream, and a migration's own test runs on a dbtest.Scratch database; its UI-assets paragraph says the member set panics at boot through template.Must, an operator handler returns the constructor's error, and the lint fails on a file neither set parses. |
||
|
|
b75c17ef81 | Document the static checks | ||
|
|
02cf4c6fc5 | Fail the project lint on a template it cannot parse | ||
|
|
b1d8f18ae2 | Retire the stale ?tab= lint rule | ||
|
|
28eb8455f9 | Delete the request-decompression middleware nothing mounts | ||
|
|
0caed6e905 |
Harvest the exploration notebooks into the docs
A survey of the twenty notebooks on disk found general facts that no tracked page stated; the maintainer struck the weak rows and the rest are written into their owning pages in each page's own words. The design-system page gains the hidden-navigation figures that replaced a dangling survey pointer, the reason the anatomy is parts and lint, the record-table grouping and single-tint rules, and when a surface earns row editing. The first-contact process gains a code-verification phase, the transcript lane for models without vision, the three kinds of honesty-audit drift and the click-path guardrail. The operator conventions gain guards-before-writes, error state over empty state, copy that states the real effect, toasts that report counts and the PostForm rule. The Stripe, FedWiki, identifiers, IA, testing, model and environment pages each gain their facts, the integration guide stops telling authors to hand-write a page title, issues.md logs the placeholder security contact, milestones.md records the Codeberg terms clause and CONTRIBUTING.md notes that the module path is the forge URL. One claimed fact was checked against the code and not written: the mass-mutation preview does not run the commit loop in a rolled-back transaction. |
||
|
|
074702c8bc |
Seed ledger demo via shared grant act helpers
Extract ExtendGrantTx, RevokeGrantTx, and ExpireGrantTx into internal/entitlements so the operator handlers, expiry activity, and demo seed all run the same conferral path. The seed plays a five-grant history on Carlos's org; pin-timestamps now uses dense-rank offsets and transition list ordering ties break by transition_id. |
||
|
|
ee23d31c97 |
Complete anatomy sweep across all surfaces
Rebuild every operator, member, and integration template on the shared page-anatomy parts, empty the lint allowlist, and move the confirm modal into both shells. Replace back links with location trails and make row identifiers the primary link. Make screen captures deterministic by resetting the app database from a pinned demo snapshot. Send the logout id_token_hint only while valid. |
||
|
|
408fa6f5a6 |
Add page anatomy parts and UI quality gate
- Add shared ui_*.html parts (pageHeader, sectionHeader, statusBadge, emptyState) parsed into every template set - Add anatomy lint rules with a shrinking allowlist and screen-coverage check - Add make screens capture harness with contact sheets and baseline diff - Compose member and FedWiki regions server-side so pages arrive complete - Rebuild Domains and Integrations on the parts as pilots |
||
|
|
60b4c93eb6 |
Remove hardcoded absolute repo paths
Use repo-relative paths in docs and exploration notes, and update the chrome-devtools launch description in testing.md. |
||
|
|
c4bb1ba585 |
Harden domain claim expiry and carving
Sweep stranded pending claims at boot and on a Temporal schedule while preserving evidence-based abandonment semantics. Apply occupancy and name-policy checks to carves by operator-root owners without affecting direct operator placements. |
||
|
|
c604487524 |
Archive migration changes and update specs
Document the consolidated core/integration schema model, per-stream goose ledgers, and revised M9 integration roadmap. |
||
|
|
5752dead03 |
Use per-stream migration ledgers
Run goose once per migration source with its own goose_db_version_<name> table and native file numbering. Remove positional namespace assembly, update rollback/status behavior, and document the OpenSpec decision. |
||
|
|
cbd08b45aa |
Add project front-door documentation
Rewrite the README around purpose, architecture, quickstart, and status. Add contributing/security policies plus a docs index with audience front matter across existing docs. |
||
|
|
966a309bfd |
docs: add testing guide
Document the kinds of tests (pure unit, DB-gated integration, Stripe-mocked, HTTP handler/render, route/template lint, live e2e, OpenSpec validation), what each covers, how to run them, and the role of the test/ stack directory. Add a Testing subsection to the README pointing at it. |