@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 +72 -11
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +2335 -36
- package/dist/index.d.cts +106 -7
- package/dist/index.d.ts +106 -7
- package/dist/index.js +2335 -36
- package/package.json +2 -2
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
1
|
+
661fda5524fdc45e8824e920730ec225c532d6c4d61b964f3fccffb7bb62b15e
|