@classytic/arc 2.10.8 → 2.11.1
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/dist/{BaseController-DVNKvoX4.mjs → BaseController-JNV08qOT.mjs} +480 -442
- package/dist/{queryCachePlugin-Dumka73q.d.mts → QueryCache-DOBNHBE0.d.mts} +2 -32
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-BXY4i-hw.mjs → adapters-D0tT2Tyo.mjs} +54 -0
- package/dist/audit/index.d.mts +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.mjs +5 -5
- package/dist/{betterAuthOpenApi--rdY15Ld.mjs → betterAuthOpenApi-DwxtK3uG.mjs} +1 -1
- package/dist/cache/index.d.mts +3 -2
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +37 -27
- package/dist/cli/commands/init.mjs +46 -33
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -3
- package/dist/core-DXdSSFW-.mjs +1037 -0
- package/dist/createActionRouter-BwaSM0No.mjs +166 -0
- package/dist/{createApp-BwnEAO2h.mjs → createApp-P1d6rjPy.mjs} +75 -27
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-Dci0AYLT.mjs → elevation-DOFoxoDs.mjs} +1 -1
- package/dist/{errorHandler-CSxe7KIM.mjs → errorHandler-BQm8ZxTK.mjs} +1 -1
- package/dist/{eventPlugin-ByU4Cv0e.mjs → eventPlugin--5HIkdPU.mjs} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +2 -2
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/index.mjs +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-C_Noptz-.d.mts → index-BYCqHCVu.d.mts} +2 -2
- package/dist/{index-BGbpGVyM.d.mts → index-C_bgx9o4.d.mts} +712 -500
- package/dist/{index-BziRPS4H.d.mts → index-CvM1e09j.d.mts} +29 -10
- package/dist/{index-EqQN6p0W.d.mts → index-pUczGjO0.d.mts} +11 -8
- package/dist/index-smCAoA5W.d.mts +1179 -0
- package/dist/index.d.mts +6 -38
- package/dist/index.mjs +9 -9
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +46 -5
- package/dist/integrations/streamline.mjs +50 -21
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +2 -154
- package/dist/integrations/websocket.mjs +292 -224
- package/dist/{keys-nWQGUTu1.mjs → keys-CARyUjiR.mjs} +2 -0
- package/dist/{loadResources-Bksk8ydA.mjs → loadResources-CPpkyKfM.mjs} +32 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/{openapi-DpNpqBmo.mjs → openapi-C0L9ar7m.mjs} +4 -4
- package/dist/org/index.d.mts +1 -1
- package/dist/permissions/index.d.mts +1 -1
- package/dist/permissions/index.mjs +2 -4
- package/dist/{permissions-wkqRwicB.mjs → permissions-B4vU9L0Q.mjs} +221 -3
- package/dist/{pipe-CGJxqDGx.mjs → pipe-DVoIheVC.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +10 -10
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +42 -24
- package/dist/presets/filesUpload.d.mts +1 -1
- package/dist/presets/filesUpload.mjs +3 -3
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +6 -0
- package/dist/presets/search.d.mts +1 -1
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-CrwOvuXI.mjs → presets-k604Lj99.mjs} +1 -1
- package/dist/queryCachePlugin-BUXBSm4F.d.mts +34 -0
- package/dist/{queryCachePlugin-ChLNZvFT.mjs → queryCachePlugin-Bq6bO6vc.mjs} +3 -3
- package/dist/{redis-MXLp1oOf.d.mts → redis-Cm1gnRDf.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{resourceToTools-BhF3JV5p.mjs → resourceToTools--okX6QBr.mjs} +534 -420
- package/dist/routerShared-DeESFp4a.mjs +515 -0
- package/dist/schemaIR-BlG9bY7v.mjs +137 -0
- package/dist/scope/index.mjs +2 -2
- package/dist/testing/index.d.mts +367 -711
- package/dist/testing/index.mjs +637 -1434
- package/dist/{tracing-xqXzWeaf.d.mts → tracing-DokiEsuz.d.mts} +9 -4
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -3
- package/dist/{types-CVdgPXBW.d.mts → types-BdA4uMBV.d.mts} +191 -28
- package/dist/{types-CVKBssX5.d.mts → types-Bh_gEJBi.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -968
- package/dist/utils/index.mjs +5 -6
- package/dist/utils-D3Yxnrwr.mjs +1639 -0
- package/dist/websocket-CyJ1VIFI.d.mts +186 -0
- package/package.json +7 -5
- package/skills/arc/SKILL.md +124 -39
- package/skills/arc/references/testing.md +212 -183
- package/dist/applyPermissionResult-QhV1Pa-g.mjs +0 -37
- package/dist/core-3MWJosCH.mjs +0 -1459
- package/dist/createActionRouter-C8UUB3Px.mjs +0 -249
- package/dist/errors-BI8kEKsO.d.mts +0 -140
- package/dist/fields-CTMWOUDt.mjs +0 -126
- package/dist/queryParser-NR__Qiju.mjs +0 -419
- package/dist/types-CDnTEpga.mjs +0 -27
- package/dist/utils-LMwVidKy.mjs +0 -947
- /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-CGsMd6oK.mjs} +0 -0
- /package/dist/{ResourceRegistry-CcN2LVrc.mjs → ResourceRegistry-DkAeAuTX.mjs} +0 -0
- /package/dist/{actionPermissions-TUVR3uiZ.mjs → actionPermissions-C8YYU92K.mjs} +0 -0
- /package/dist/{caching-3h93rkJM.mjs → caching-CheW3m-S.mjs} +0 -0
- /package/dist/{errorHandler-2ii4RIYr.d.mts → errorHandler-Co3lnVmJ.d.mts} +0 -0
- /package/dist/{errors-BqdUDja_.mjs → errors-D5c-5BJL.mjs} +0 -0
- /package/dist/{eventPlugin-D1ThQ1Pp.d.mts → eventPlugin-CUNjYYRY.d.mts} +0 -0
- /package/dist/{interface-B-pe8fhj.d.mts → interface-CkkWm5uR.d.mts} +0 -0
- /package/dist/{interface-yhyb_pLY.d.mts → interface-Da0r7Lna.d.mts} +0 -0
- /package/dist/{memory-DqI-449b.mjs → memory-DikHSvWa.mjs} +0 -0
- /package/dist/{metrics-TuOmguhi.mjs → metrics-Csh4nsvv.mjs} +0 -0
- /package/dist/{multipartBody-CUQGVlM_.mjs → multipartBody-CvTR1Un6.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-BneOJkpi.mjs} +0 -0
- /package/dist/{redis-stream-bkO88VHx.d.mts → redis-stream-CM8TXTix.d.mts} +0 -0
- /package/dist/{registry-B0Wl7uVV.mjs → registry-D63ee7fl.mjs} +0 -0
- /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-ByllIXXV.mjs} +0 -0
- /package/dist/{requestContext-C38GskNt.mjs → requestContext-CfRkaxwf.mjs} +0 -0
- /package/dist/{schemaConverter-BxFDdtXu.mjs → schemaConverter-B0oKLuqI.mjs} +0 -0
- /package/dist/{sse-D8UeDwis.mjs → sse-V7aXc3bW.mjs} +0 -0
- /package/dist/{store-helpers-DYYUQbQN.mjs → store-helpers-BhrzxvyQ.mjs} +0 -0
- /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
- /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
- /package/dist/{versioning-B6mimogM.mjs → versioning-CGPjkqAg.mjs} +0 -0
- /package/dist/{versioning-CeUXHfjw.d.mts → versioning-M9lNLhO8.d.mts} +0 -0
|
@@ -1,183 +1,212 @@
|
|
|
1
|
-
# Arc Testing Utilities
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
import {
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1
|
+
# Arc Testing Utilities (2.11)
|
|
2
|
+
|
|
3
|
+
Three primary entry points — pick by what you're testing. Everything else composes with one of them.
|
|
4
|
+
|
|
5
|
+
| Entry point | Use when | Tests in scope |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `createHttpTestHarness(resource, ctxFn)` | You want auto-generated CRUD + permission + validation coverage per resource | ~16 tests / resource, zero boilerplate |
|
|
8
|
+
| `createTestApp({ resources, authMode, db })` | Custom scenarios, end-to-end flows, integration across resources | You write assertions with `expectArc(res)` |
|
|
9
|
+
| `runStorageContract(setup)` | You're building an adapter and want to prove it satisfies arc's Storage contract | DB-agnostic adapter conformance |
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## `createTestApp()` — turnkey Fastify + in-memory Mongo + auth + fixtures
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createTestApp, expectArc } from '@classytic/arc/testing';
|
|
17
|
+
import type { TestAppContext } from '@classytic/arc/testing';
|
|
18
|
+
|
|
19
|
+
describe('Product API', () => {
|
|
20
|
+
let ctx: TestAppContext;
|
|
21
|
+
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
ctx = await createTestApp({
|
|
24
|
+
resources: [productResource],
|
|
25
|
+
authMode: 'jwt', // 'jwt' | 'better-auth' | 'none'
|
|
26
|
+
db: 'in-memory', // default; or { uri } | false
|
|
27
|
+
connectMongoose: true, // optional — one-liner for Mongoose apps
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
ctx.auth.register('admin', {
|
|
31
|
+
user: { id: '1', roles: ['admin'] },
|
|
32
|
+
orgId: 'org-1',
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterAll(() => ctx.close());
|
|
37
|
+
|
|
38
|
+
it('GET /products — public', async () => {
|
|
39
|
+
const res = await ctx.app.inject({ method: 'GET', url: '/products' });
|
|
40
|
+
expectArc(res).ok().paginated();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('POST /products — admin required', async () => {
|
|
44
|
+
const res = await ctx.app.inject({
|
|
45
|
+
method: 'POST',
|
|
46
|
+
url: '/products',
|
|
47
|
+
headers: ctx.auth.as('admin').headers,
|
|
48
|
+
payload: { name: 'Widget', price: 99 },
|
|
49
|
+
});
|
|
50
|
+
expectArc(res).ok();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**`TestAppContext` shape**: `{ app, auth, fixtures, dbUri, close }`. Auth is `undefined` when `authMode: 'none'`; `dbUri` is present when `db: 'in-memory'` or `{ uri }`.
|
|
56
|
+
|
|
57
|
+
**`db` modes**:
|
|
58
|
+
- `'in-memory'` (default) — boots `MongoMemoryServer`, exposes `dbUri`, stops on `close()`. Needs `npm i -D mongodb-memory-server`.
|
|
59
|
+
- `{ uri }` — external Mongo URI; caller owns lifecycle.
|
|
60
|
+
- `false` — no DB wiring (pure Fastify unit tests).
|
|
61
|
+
|
|
62
|
+
**`authMode: 'better-auth'`** requires the caller to also pass `auth: { type: 'better-auth', ... }`. Mismatched config fails fast at setup.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## `TestAuthProvider` — unified session primitive
|
|
67
|
+
|
|
68
|
+
One `register()` → `.as(role).headers` flow across JWT, Better Auth, and custom providers.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// JWT — provider signs on-the-fly via app.jwt.sign()
|
|
72
|
+
ctx.auth.register('admin', { user: { id: '1', roles: ['admin'] }, orgId: 'org-1' });
|
|
73
|
+
ctx.auth.register('user', { user: { id: '2', roles: ['user'] } });
|
|
74
|
+
|
|
75
|
+
// Better Auth / custom — pre-signed tokens
|
|
76
|
+
ctx.auth.register('admin', { token: existingToken, orgId: 'org-1' });
|
|
77
|
+
|
|
78
|
+
// Use
|
|
79
|
+
const headers = ctx.auth.as('admin').headers; // { authorization, x-organization-id }
|
|
80
|
+
const withExtra = ctx.auth.as('admin').withExtra({ 'x-request-id': 'r-1' }).headers;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Directly construct without `createTestApp`:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { createJwtAuthProvider, createBetterAuthProvider } from '@classytic/arc/testing';
|
|
87
|
+
const auth = createJwtAuthProvider(app, { defaultOrgId: 'org-1' });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## `createHttpTestHarness(resource, ctxFn)` — auto-generated resource coverage
|
|
93
|
+
|
|
94
|
+
~16 tests per resource (CRUD + permission + validation + error envelope) from a single factory call. Reads `defineResource()` config and probes every route.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { createTestApp, createHttpTestHarness } from '@classytic/arc/testing';
|
|
98
|
+
|
|
99
|
+
describe('Product resource — full coverage', () => {
|
|
100
|
+
const ctx = await createTestApp({ resources: [productResource] });
|
|
101
|
+
ctx.auth.register('admin', { user: { id: '1', roles: ['admin'] } });
|
|
102
|
+
|
|
103
|
+
createHttpTestHarness(productResource, () => ({
|
|
104
|
+
app: ctx.app,
|
|
105
|
+
auth: ctx.auth,
|
|
106
|
+
adminRole: 'admin',
|
|
107
|
+
fixtures: { product: (i) => ({ name: `P${i}`, price: 10 + i }) },
|
|
108
|
+
})).runAll();
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## `expectArc(response)` — fluent envelope matchers
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
expectArc(res).ok(); // 200/201, success: true
|
|
118
|
+
expectArc(res).forbidden(); // 403, arc error envelope
|
|
119
|
+
expectArc(res).notFound().hasError(/not exist/);
|
|
120
|
+
expectArc(res).validationError().hasData({ fields: ['email'] });
|
|
121
|
+
expectArc(res).paginated({ total: 10 }); // meta.pagination present
|
|
122
|
+
expectArc(res).hidesField('password'); // field stripped from response
|
|
123
|
+
expectArc(res).hasMeta('traceId');
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Available: `.ok`, `.failed`, `.unauthorized`, `.forbidden`, `.notFound`, `.validationError`, `.conflict`, `.hasData`, `.hidesField`, `.showsField`, `.paginated`, `.hasError`, `.hasMeta`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## `createTestFixtures()` — DB-agnostic seeding
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { createTestFixtures } from '@classytic/arc/testing';
|
|
134
|
+
|
|
135
|
+
const fixtures = createTestFixtures();
|
|
136
|
+
fixtures.register('product', async (data) => {
|
|
137
|
+
const doc = await Product.create(data);
|
|
138
|
+
return { record: doc, destroy: () => Product.deleteOne({ _id: doc._id }) };
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const widget = await fixtures.create('product', { name: 'Widget' });
|
|
142
|
+
await fixtures.clear(); // runs destroyers newest-first
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Destroyers bind at create time — no global cleanup registry. Works with any backend (Mongoose, sqlitekit, Prisma, in-memory).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Better Auth orchestration — `setupBetterAuthTestApp`
|
|
150
|
+
|
|
151
|
+
Composes `createTestApp` with Better Auth sign-up + org creation. Use when you need real Better Auth tokens rather than pre-signed stubs:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { setupBetterAuthTestApp, createBetterAuthTestHelpers } from '@classytic/arc/testing';
|
|
155
|
+
|
|
156
|
+
const { ctx, helpers } = await setupBetterAuthTestApp({ resources: [orderResource], auth });
|
|
157
|
+
const { user, token, orgId } = await helpers.signUpWithOrg({ email: 'a@x.co', name: 'A' });
|
|
158
|
+
ctx.auth.register('admin', { token, orgId });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Mocks (non-Fastify unit tests)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import {
|
|
167
|
+
createMockRepository,
|
|
168
|
+
createDataFactory,
|
|
169
|
+
createMockUser,
|
|
170
|
+
createMockRequest,
|
|
171
|
+
createMockReply,
|
|
172
|
+
waitFor,
|
|
173
|
+
createSpy,
|
|
174
|
+
createTestTimer,
|
|
175
|
+
} from '@classytic/arc/testing';
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## `runStorageContract(setup)` — adapter conformance
|
|
181
|
+
|
|
182
|
+
DB-agnostic Storage contract check. Build a setup that returns your adapter factory + a cleanup fn; arc runs the full contract suite against it.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { runStorageContract } from '@classytic/arc/testing';
|
|
186
|
+
runStorageContract(async () => {
|
|
187
|
+
const storage = createMyAdapter();
|
|
188
|
+
return { storage, cleanup: async () => storage.close() };
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Tips
|
|
195
|
+
|
|
196
|
+
1. **`app.inject()` over real HTTP** — same server, zero network.
|
|
197
|
+
2. **Register auth sessions once per role** — not per test.
|
|
198
|
+
3. **Use `ctx.fixtures.clear()` in `afterEach`** — destroyers handle dependency order.
|
|
199
|
+
4. **Test permission denials explicitly** — `expectArc(res).forbidden()` beats status-code assertions.
|
|
200
|
+
5. **Reach for `createHttpTestHarness` first** — 16 tests for one function call.
|
|
201
|
+
|
|
202
|
+
## Migration from pre-2.11 testing APIs
|
|
203
|
+
|
|
204
|
+
| Pre-2.11 | 2.11 |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `TestHarness` / `createTestHarness` | `createHttpTestHarness(resource, ctxFn)` |
|
|
207
|
+
| `TestAppResult` | `TestAppContext` |
|
|
208
|
+
| `testApp.mongoUri` | `ctx.dbUri` |
|
|
209
|
+
| `createJwtAuthProvider` / `createBetterAuthProvider` (as `HttpTestHarness` imports) | `ctx.auth` from `createTestApp` (or direct factory import) |
|
|
210
|
+
| `withTestDb()` | `createTestApp({ db: 'in-memory' })` + `ctx.dbUri` |
|
|
211
|
+
| `TestDatabase` / `TestSeeder` / `TestTransaction` | `createTestFixtures` + kit-level cleanup |
|
|
212
|
+
| `setupBetterAuthOrg` | `setupBetterAuthTestApp` + `helpers.signUpWithOrg` |
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
//#region src/permissions/applyPermissionResult.ts
|
|
2
|
-
/**
|
|
3
|
-
* Normalize a permission check return value (`boolean | PermissionResult`)
|
|
4
|
-
* into a concrete `PermissionResult`. This is the only place in Arc that
|
|
5
|
-
* promotes booleans to results — keeps the type narrowing honest everywhere.
|
|
6
|
-
*/
|
|
7
|
-
function normalizePermissionResult(result) {
|
|
8
|
-
if (typeof result === "boolean") return { granted: result };
|
|
9
|
-
return result;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Apply a granted `PermissionResult` to a Fastify request — merges row-level
|
|
13
|
-
* filters into `_policyFilters` and conditionally installs the scope.
|
|
14
|
-
*
|
|
15
|
-
* **Scope install rule:** only writes `scope` when the current request scope
|
|
16
|
-
* is absent or `public`. This prevents downgrading an already-authenticated
|
|
17
|
-
* request (e.g. Better Auth set `member`, then a permission check returns a
|
|
18
|
-
* narrower `service` scope — the original `member` wins because it came from
|
|
19
|
-
* a more authoritative source).
|
|
20
|
-
*
|
|
21
|
-
* Safe to call with a non-granted result — it simply no-ops. Callers should
|
|
22
|
-
* still check `result.granted` and send an error response before reaching here,
|
|
23
|
-
* but this function tolerates the misuse defensively.
|
|
24
|
-
*/
|
|
25
|
-
function applyPermissionResult(result, request) {
|
|
26
|
-
if (!result.granted) return;
|
|
27
|
-
if (result.filters) request._policyFilters = {
|
|
28
|
-
...request._policyFilters ?? {},
|
|
29
|
-
...result.filters
|
|
30
|
-
};
|
|
31
|
-
if (result.scope) {
|
|
32
|
-
const current = request.scope;
|
|
33
|
-
if (!current || current.kind === "public") request.scope = result.scope;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
37
|
-
export { normalizePermissionResult as n, applyPermissionResult as t };
|