@saasicat/persistence-testing 1.0.0-rc.2 → 1.0.0-rc.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @saasicat/persistence-testing
2
2
 
3
+ ## What this is
4
+
3
5
  The executable persistence contract for SaaSiCat adapters. One `node:test`
4
6
  suite that every adapter (Prisma and Drizzle ship today, TypeORM plugs into
5
7
  the same harness) must pass **against a real database** — this is what makes
@@ -14,17 +16,51 @@ Verified scenarios:
14
16
  - PlanVersion and BundleVersion validity windows with auto-succession
15
17
  - `countByPlanVersionId` counts current AND pending bindings in one query
16
18
  - transaction rollback discards writes
19
+ - a contract written on a transaction is undone with it, and found by the offer it came from
20
+ - a checkout offer is consumed once, whoever asks first, and a consume on a rolled-back transaction
21
+ leaves it open
17
22
  - `findByTenantIdLocked` serializes concurrent transactions (row lock)
18
23
  - concurrent `claimSlot` grants exactly `maxRedemptions` slots
19
24
  - claim / exhaust / release lifecycle
20
25
  - one promo redemption per subscription (unique guard)
21
26
  - audit write → query roundtrip incl. `actorTag` wildcard filters
22
27
  - MFA secret roundtrip
28
+ - a gateway event is claimed once per account, a session is confirmed once however many events
29
+ report it, an event that changed nothing gives its session back while staying claimed itself, and
30
+ a claim rolled back with its transaction is free for the retry
31
+ - a confirmed payment method replaces the one in use and keeps it as history, per subscriber
32
+ - a change of payment method a tenant started is completed once, and only for the account, session
33
+ and subscriber it was started for
34
+ - the applied settings: one row per installation, replaced only by a writer that
35
+ read its current fingerprint — so replicas starting together record one change,
36
+ concurrently — with the change and the record it supersedes landing together;
37
+ changes listed in the order they were recorded, acknowledged once
38
+
39
+ Scenario groups gate on declared capabilities and provided slices. A group the
40
+ capabilities rule out, such as the lock scenarios with `pessimisticLocking:
41
+ false`, reports as **skipped with reason**. A group whose port or seed writer
42
+ the harness does not provide **fails**, unless the adapter names it in `gaps` —
43
+ then it reports as skipped. A gap named there that the harness does provide
44
+ fails the suite, so the list stays true. A skipped scenario is easy to read
45
+ past in a green run; a harness that forgot to wire a port would otherwise pass
46
+ without checking it.
47
+
48
+ The list describes the harness as it is built, not the adapter package. Where a
49
+ port adds a member only under an option — `@saasicat/adapter-prisma`'s
50
+ `validityWindows` and `atomicOnboardingSelection`, off by default for a 0.6
51
+ schema — compute `gaps` from the same option rather than writing a constant, so
52
+ the declaration moves when the schema does.
53
+
54
+ ## What this is not
23
55
 
24
- Scenario groups gate on declared capabilities and provided slices; a
25
- gated-off group reports as **skipped with reason** coverage gaps stay
26
- visible, never silent. Roadmap scenarios (subscription contracts, reference
27
- migrations N→N+1) are registered as visible skips until the slices ship.
56
+ Not a test framework and not a set of fixtures for your application. It is one
57
+ executable contract: the suite an adapter must pass before it can claim to
58
+ implement the ports.
59
+
60
+ Not runnable without a database. The semantics it checks — row locks, rollback,
61
+ atomic promo redemption, tenant isolation — are exactly the ones an in-memory
62
+ double cannot have, so it needs a real PostgreSQL and says so when it does not
63
+ find one.
28
64
 
29
65
  ## Usage
30
66
 
@@ -32,27 +68,46 @@ migrations N→N+1) are registered as visible skips until the slices ship.
32
68
  import { persistenceAdapterContract } from '@saasicat/persistence-testing';
33
69
 
34
70
  persistenceAdapterContract({
35
- name: 'adapter-drizzle @ postgres',
36
- projectKey: 'my-app',
71
+ name: 'my-adapter @ postgres',
37
72
  create: async () => ({
38
73
  adapter: {
39
74
  capabilities: { transactions: true, pessimisticLocking: true /* … */ },
40
75
  transactionRunner,
41
76
  subscriptionRepository,
42
77
  planVersionRepository,
43
- promoCodeRepository, // optional slices activate more scenarios
78
+ planRepository,
79
+ bundleRepository,
80
+ subscriptionBundleRepository,
81
+ tenantSubscriptionWrite,
82
+ promoCodeRepository,
44
83
  promoCodeRedemptionRepository,
84
+ promoSubscriptionLookup,
45
85
  mfa,
46
86
  audit,
47
87
  auditQuery,
48
- tenantSubscriptionWrite, // optional: enables atomic plan-binding scenarios
49
- planRepository, // optional: enables plan lifecycle scenarios
50
- bundleRepository, // optional: enables bundle validity scenarios
88
+ // Leave a part out and name it in `gaps` below; left out and not
89
+ // named, its scenarios fail.
90
+ },
91
+ seed: {
92
+ createPlanVersion,
93
+ createSubscription,
94
+ createBundleVersion,
95
+ clearBookingRequestDate,
96
+ createPromoCode,
51
97
  },
52
- seed: { createPlanVersion, createSubscription, createPromoCode },
53
98
  reset: () => truncatePlatformTables(),
54
99
  close: () => pool.end(),
55
100
  }),
101
+ // The parts this adapter deliberately does not provide. A part named here
102
+ // that the harness does provide fails the suite as well.
103
+ gaps: [
104
+ 'subscriptionContracts',
105
+ 'subscribers',
106
+ 'paymentEventLog',
107
+ 'subscriberPaymentMethods',
108
+ 'checkoutOffers',
109
+ 'appliedSettings',
110
+ ],
56
111
  });
57
112
  ```
58
113
 
@@ -64,3 +119,9 @@ fixture writes are schema-specific. See
64
119
  In-memory fakes must not be used to "verify" an adapter — they cannot
65
120
  emulate lock semantics (declare `pessimisticLocking: false` and the lock
66
121
  scenarios gate off, visibly).
122
+
123
+ ## Next
124
+
125
+ - [Ports and adapters](../../docs/explanation/adr/0007-ports-and-adapters.md) — what a port promises
126
+ - [Test coverage](../../docs/explanation/test-coverage.md) — what this contract reaches, and what it
127
+ does not
package/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- 43f727faea9d157b3ac6d065838ecaa4035ae6ae630dbcc6a579fa6f2a3d5b74
1
+ 661fda5524fdc45e8824e920730ec225c532d6c4d61b964f3fccffb7bb62b15e