@classytic/arc 2.28.0 → 2.30.0
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-B65zbz9m.mjs → BaseController-B5DwufbB.mjs} +12 -10
- package/dist/EventTransport-C6YA8xY9.mjs +103 -0
- package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-WkIougo6.mjs} +15 -35
- package/dist/applyPermissionResult-Cf6zUCvb.mjs +257 -0
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +1 -1
- package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-BYS3Vkfz.mjs} +32 -6
- package/dist/cleanup/index.d.mts +2 -2
- package/dist/cleanup/index.mjs +1 -1
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +5 -4
- package/dist/{core-BGa8TXJR.mjs → core-poalJLvp.mjs} +2 -48
- package/dist/{createActionRouter-C-N7z2EL.mjs → createActionRouter-BxuywVRB.mjs} +2 -2
- package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-BiY-lGdY.mjs} +7 -20
- package/dist/{createApp-Du5G54TM.mjs → createApp-CXrKFciH.mjs} +24 -3
- package/dist/defineAggregation-ufWjUwml.mjs +47 -0
- package/dist/{defineResource-2zvVVpxg.mjs → defineResource-CxWqDCAy.mjs} +35 -42
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +1 -1
- package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-D2NNR4py.mjs} +2 -103
- package/dist/events/index.mjs +4 -339
- package/dist/factory/index.d.mts +4 -4
- package/dist/factory/index.mjs +1 -1
- package/dist/{fastify-augmentation-OzYGIiU1.d.mts → fastify-augmentation-0WhPnUH7.d.mts} +3 -3
- package/dist/{applyPermissionResult-BnyVE9eD.mjs → fields-CQ_7KQWw.mjs} +1 -107
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.mjs +1 -1
- package/dist/{index-CZY4EGga.d.mts → index-CCzVR-N3.d.mts} +1 -1
- package/dist/{index-BMnbzvtO.d.mts → index-Ch16aAah.d.mts} +201 -21
- package/dist/{index-CjVg-Epg.d.mts → index-Cx7bro4R.d.mts} +4 -4
- package/dist/{index-DPRH0Cme.d.mts → index-Q5gzEIWY.d.mts} +97 -66
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +8 -7
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +6 -6
- 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/{mcpPlugin-Cs6nRGCt.mjs → mcpPlugin-nRzm5g2s.mjs} +36 -43
- package/dist/middleware/index.d.mts +1 -1
- package/dist/migrations/index.d.mts +0 -3
- package/dist/migrations/index.mjs +0 -3
- package/dist/{openapi-BLV4OMXb.mjs → openapi-B8Vkbxbn.mjs} +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +4 -3
- package/dist/{permissions-BrtiBKGs.mjs → permissions-Bfu_8y1U.mjs} +318 -289
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/{pipeline-BGRnV-To.mjs → pipeline-BJnpkY9i.mjs} +4 -18
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{preloadResources-CM4uUaFv.d.mts → preloadResources-DCfa2egz.d.mts} +3 -3
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +14 -2
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-DwFDVKP0.mjs → presets-CJOEXLuk.mjs} +25 -9
- package/dist/projection-UBDgfMwu.mjs +17 -0
- package/dist/{realtime-DEuDu9io.mjs → realtime-B9nmIY5V.mjs} +2 -1
- package/dist/{registerRoute-DTp18Ah_.mjs → registerRoute-JkxIEBJq.mjs} +3 -1
- package/dist/registry/index.d.mts +2 -2
- package/dist/registry/index.mjs +1 -1
- package/dist/{registry-9km_w1aP.mjs → registry-BYXUUg21.mjs} +1 -1
- package/dist/repositoryFilter-Dz8k3S4P.mjs +44 -0
- package/dist/subscribe-helpers-DfzsivM6.mjs +339 -0
- package/dist/testing/index.d.mts +34 -6
- package/dist/testing/index.mjs +314 -211
- package/dist/types/index.d.mts +3 -3
- package/dist/{types-QFGc3Cjv.d.mts → types-Bmmn-fO0.d.mts} +1 -1
- package/dist/{types-Dmw-6Qeo.d.mts → types-ChvvVNP8.d.mts} +93 -88
- package/dist/{types-Cu6vVJmh.d.mts → types-D_n8L6VY.d.mts} +32 -1
- package/dist/usage/index.d.mts +1 -1
- package/dist/usage/index.mjs +0 -0
- package/dist/utils/index.d.mts +2 -5
- package/package.json +539 -539
package/dist/testing/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { t as CRUD_OPERATIONS } from "../constants-DNre0wRg.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { t as BaseController } from "../BaseController-B5DwufbB.mjs";
|
|
3
|
+
import { t as defineResource } from "../defineResource-CxWqDCAy.mjs";
|
|
4
|
+
import { t as defineAggregation } from "../defineAggregation-ufWjUwml.mjs";
|
|
5
|
+
import { n as createApp } from "../createApp-CXrKFciH.mjs";
|
|
3
6
|
import { n as getModuleExports } from "../module-_-M7C5ES.mjs";
|
|
4
7
|
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-DSY5HGpS.mjs";
|
|
5
8
|
import { runStorageContract } from "./storageContract.mjs";
|
|
@@ -125,6 +128,314 @@ function expectArc(response) {
|
|
|
125
128
|
return assertion;
|
|
126
129
|
}
|
|
127
130
|
//#endregion
|
|
131
|
+
//#region src/testing/mocks.ts
|
|
132
|
+
/**
|
|
133
|
+
* Create a mock repository for testing
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* const mockRepo = createMockRepository<Product>({
|
|
137
|
+
* getById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
|
|
138
|
+
* create: vi.fn().mockImplementation(data => Promise.resolve({ id: '1', ...data })),
|
|
139
|
+
* });
|
|
140
|
+
*
|
|
141
|
+
* await mockRepo.getById('1'); // Returns mocked product
|
|
142
|
+
*/
|
|
143
|
+
function createMockRepository(overrides = {}) {
|
|
144
|
+
return {
|
|
145
|
+
getAll: vi.fn().mockResolvedValue({
|
|
146
|
+
method: "offset",
|
|
147
|
+
data: [],
|
|
148
|
+
total: 0,
|
|
149
|
+
page: 1,
|
|
150
|
+
limit: 20,
|
|
151
|
+
pages: 0,
|
|
152
|
+
hasNext: false,
|
|
153
|
+
hasPrev: false
|
|
154
|
+
}),
|
|
155
|
+
getById: vi.fn().mockResolvedValue(null),
|
|
156
|
+
create: vi.fn().mockImplementation((data) => Promise.resolve({
|
|
157
|
+
_id: "mock-id",
|
|
158
|
+
...data
|
|
159
|
+
})),
|
|
160
|
+
update: vi.fn().mockImplementation((_id, data) => Promise.resolve({
|
|
161
|
+
_id: "mock-id",
|
|
162
|
+
...data
|
|
163
|
+
})),
|
|
164
|
+
delete: vi.fn().mockResolvedValue({
|
|
165
|
+
success: true,
|
|
166
|
+
message: "Deleted"
|
|
167
|
+
}),
|
|
168
|
+
updateMany: vi.fn().mockResolvedValue({
|
|
169
|
+
acknowledged: true,
|
|
170
|
+
matchedCount: 0,
|
|
171
|
+
modifiedCount: 0
|
|
172
|
+
}),
|
|
173
|
+
deleteMany: vi.fn().mockResolvedValue({
|
|
174
|
+
acknowledged: true,
|
|
175
|
+
deletedCount: 0
|
|
176
|
+
}),
|
|
177
|
+
claim: vi.fn().mockResolvedValue(null),
|
|
178
|
+
claimVersion: vi.fn().mockResolvedValue(null),
|
|
179
|
+
capabilities: {
|
|
180
|
+
transactions: false,
|
|
181
|
+
nestedTransactions: false,
|
|
182
|
+
upsert: false,
|
|
183
|
+
duplicateKeyError: false,
|
|
184
|
+
distinct: false,
|
|
185
|
+
aggregate: false,
|
|
186
|
+
getOrCreate: false,
|
|
187
|
+
countAndExists: false
|
|
188
|
+
},
|
|
189
|
+
getBySlug: vi.fn().mockResolvedValue(null),
|
|
190
|
+
getDeleted: vi.fn().mockResolvedValue([]),
|
|
191
|
+
restore: vi.fn().mockResolvedValue(null),
|
|
192
|
+
getTree: vi.fn().mockResolvedValue([]),
|
|
193
|
+
getChildren: vi.fn().mockResolvedValue([]),
|
|
194
|
+
...overrides
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Create a mock user for authentication testing
|
|
199
|
+
*/
|
|
200
|
+
function createMockUser(overrides = {}) {
|
|
201
|
+
return {
|
|
202
|
+
_id: "mock-user-id",
|
|
203
|
+
id: "mock-user-id",
|
|
204
|
+
email: "test@example.com",
|
|
205
|
+
roles: ["user"],
|
|
206
|
+
organizationId: null,
|
|
207
|
+
...overrides
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Create a mock Fastify request
|
|
212
|
+
*/
|
|
213
|
+
function createMockRequest(overrides = {}) {
|
|
214
|
+
return {
|
|
215
|
+
body: {},
|
|
216
|
+
params: {},
|
|
217
|
+
query: {},
|
|
218
|
+
headers: {},
|
|
219
|
+
user: createMockUser(),
|
|
220
|
+
context: {},
|
|
221
|
+
log: {
|
|
222
|
+
info: vi.fn(),
|
|
223
|
+
warn: vi.fn(),
|
|
224
|
+
error: vi.fn(),
|
|
225
|
+
debug: vi.fn()
|
|
226
|
+
},
|
|
227
|
+
...overrides
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Create a mock Fastify reply
|
|
232
|
+
*/
|
|
233
|
+
function createMockReply() {
|
|
234
|
+
return {
|
|
235
|
+
code: vi.fn().mockReturnThis(),
|
|
236
|
+
send: vi.fn().mockReturnThis(),
|
|
237
|
+
header: vi.fn().mockReturnThis(),
|
|
238
|
+
headers: vi.fn().mockReturnThis(),
|
|
239
|
+
status: vi.fn().mockReturnThis(),
|
|
240
|
+
type: vi.fn().mockReturnThis(),
|
|
241
|
+
redirect: vi.fn().mockReturnThis(),
|
|
242
|
+
callNotFound: vi.fn().mockReturnThis(),
|
|
243
|
+
sent: false
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Create a mock controller for testing
|
|
248
|
+
*/
|
|
249
|
+
function createMockController(repository) {
|
|
250
|
+
return {
|
|
251
|
+
repository,
|
|
252
|
+
list: vi.fn(),
|
|
253
|
+
get: vi.fn(),
|
|
254
|
+
create: vi.fn(),
|
|
255
|
+
update: vi.fn(),
|
|
256
|
+
delete: vi.fn()
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Create mock data factory
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* const productFactory = createDataFactory<Product>({
|
|
264
|
+
* name: () => faker.commerce.productName(),
|
|
265
|
+
* price: () => faker.number.int({ min: 10, max: 1000 }),
|
|
266
|
+
* sku: (i) => `SKU-${i}`,
|
|
267
|
+
* });
|
|
268
|
+
*
|
|
269
|
+
* const product = productFactory.build();
|
|
270
|
+
* const products = productFactory.buildMany(10);
|
|
271
|
+
*/
|
|
272
|
+
function createDataFactory(template) {
|
|
273
|
+
let counter = 0;
|
|
274
|
+
return {
|
|
275
|
+
build(overrides = {}) {
|
|
276
|
+
const index = counter++;
|
|
277
|
+
const data = {};
|
|
278
|
+
for (const [key, generator] of Object.entries(template)) data[key] = generator(index);
|
|
279
|
+
return {
|
|
280
|
+
...data,
|
|
281
|
+
...overrides
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
buildMany(count, overrides = {}) {
|
|
285
|
+
return Array.from({ length: count }, () => this.build(overrides));
|
|
286
|
+
},
|
|
287
|
+
reset() {
|
|
288
|
+
counter = 0;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Create a spy that tracks function calls
|
|
294
|
+
*
|
|
295
|
+
* Useful for testing side effects without full mocking
|
|
296
|
+
*/
|
|
297
|
+
function createSpy(_name = "spy") {
|
|
298
|
+
const calls = [];
|
|
299
|
+
const spy = vi.fn((...args) => {
|
|
300
|
+
calls.push(args);
|
|
301
|
+
});
|
|
302
|
+
spy.getCalls = () => calls;
|
|
303
|
+
spy.getLastCall = () => calls[calls.length - 1] || [];
|
|
304
|
+
return spy;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Wait for a condition to be true
|
|
308
|
+
*
|
|
309
|
+
* Useful for async testing
|
|
310
|
+
*/
|
|
311
|
+
async function waitFor(condition, options = {}) {
|
|
312
|
+
const { timeout = 5e3, interval = 100 } = options;
|
|
313
|
+
const startTime = Date.now();
|
|
314
|
+
while (Date.now() - startTime < timeout) {
|
|
315
|
+
if (await condition()) return;
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
317
|
+
}
|
|
318
|
+
throw new Error(`Timeout waiting for condition after ${timeout}ms`);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Create a test timer that can be controlled
|
|
322
|
+
*/
|
|
323
|
+
function createTestTimer() {
|
|
324
|
+
let time = Date.now();
|
|
325
|
+
return {
|
|
326
|
+
now: () => time,
|
|
327
|
+
advance: (ms) => {
|
|
328
|
+
time += ms;
|
|
329
|
+
},
|
|
330
|
+
set: (timestamp) => {
|
|
331
|
+
time = timestamp;
|
|
332
|
+
},
|
|
333
|
+
reset: () => {
|
|
334
|
+
time = Date.now();
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/testing/authorizationConformance.ts
|
|
340
|
+
/**
|
|
341
|
+
* Cross-surface authorization conformance (arc 2.30).
|
|
342
|
+
*
|
|
343
|
+
* The regression wall that keeps "partial enforcement" from ever coming back:
|
|
344
|
+
* run the SAME permission through more than one arc surface and prove it enforces
|
|
345
|
+
* identically. Reviews kept finding aggregation (and MCP) drifting from CRUD —
|
|
346
|
+
* this makes that a one-call assertion for arc AND for host resources.
|
|
347
|
+
*
|
|
348
|
+
* `runAuthorizationConformance` boots a throwaway Fastify app with one resource
|
|
349
|
+
* whose `list` op AND an `aggregation` share the given permission, backed by a
|
|
350
|
+
* capturing mock repo (no DB). It returns, per surface, the HTTP status and the
|
|
351
|
+
* exact filter arc handed the repository — so the caller asserts:
|
|
352
|
+
* - deny → both surfaces fail closed with the SAME status;
|
|
353
|
+
* - allow + `policy` → the row policy reaches BOTH the list query and the
|
|
354
|
+
* aggregation `$match`, identically.
|
|
355
|
+
*
|
|
356
|
+
* Runner-agnostic: it returns data, it does not assert. Pair with your test
|
|
357
|
+
* framework's expectations (see `tests/core/authorization-conformance.test.ts`).
|
|
358
|
+
*/
|
|
359
|
+
/**
|
|
360
|
+
* Boot a one-resource app gated by `permission` on both list + an aggregation,
|
|
361
|
+
* hit both surfaces anonymously, and report what arc enforced on each.
|
|
362
|
+
*/
|
|
363
|
+
async function runAuthorizationConformance(opts) {
|
|
364
|
+
const rows = opts.rows ?? [{ _id: "1" }];
|
|
365
|
+
let listFilter;
|
|
366
|
+
let aggFilter;
|
|
367
|
+
const repo = createMockRepository({
|
|
368
|
+
getAll: (async (params = {}) => {
|
|
369
|
+
listFilter = params.filters;
|
|
370
|
+
return {
|
|
371
|
+
method: "offset",
|
|
372
|
+
data: rows,
|
|
373
|
+
total: rows.length,
|
|
374
|
+
page: 1,
|
|
375
|
+
limit: 20,
|
|
376
|
+
pages: 1,
|
|
377
|
+
hasNext: false,
|
|
378
|
+
hasPrev: false
|
|
379
|
+
};
|
|
380
|
+
}),
|
|
381
|
+
aggregate: (async (req = {}) => {
|
|
382
|
+
aggFilter = req.filter;
|
|
383
|
+
return { rows: [{ tally: rows.length }] };
|
|
384
|
+
}),
|
|
385
|
+
capabilities: {
|
|
386
|
+
count: false,
|
|
387
|
+
exists: false,
|
|
388
|
+
distinct: false,
|
|
389
|
+
aggregate: true,
|
|
390
|
+
transactions: false,
|
|
391
|
+
lookup: false,
|
|
392
|
+
softDelete: false
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
const resource = defineResource({
|
|
396
|
+
name: "conf",
|
|
397
|
+
prefix: "/confs",
|
|
398
|
+
adapter: { repository: repo },
|
|
399
|
+
controller: new BaseController(repo, {
|
|
400
|
+
resourceName: "conf",
|
|
401
|
+
tenantField: false
|
|
402
|
+
}),
|
|
403
|
+
permissions: {
|
|
404
|
+
list: opts.permission,
|
|
405
|
+
get: opts.permission,
|
|
406
|
+
create: opts.permission,
|
|
407
|
+
update: opts.permission,
|
|
408
|
+
delete: opts.permission
|
|
409
|
+
},
|
|
410
|
+
aggregations: { tally: defineAggregation({
|
|
411
|
+
measures: { tally: { op: "count" } },
|
|
412
|
+
permissions: opts.permission
|
|
413
|
+
}) }
|
|
414
|
+
});
|
|
415
|
+
const app = Fastify({ logger: false });
|
|
416
|
+
await app.register(resource.toPlugin());
|
|
417
|
+
await app.ready();
|
|
418
|
+
const list = await app.inject({
|
|
419
|
+
method: "GET",
|
|
420
|
+
url: "/confs"
|
|
421
|
+
});
|
|
422
|
+
const aggregation = await app.inject({
|
|
423
|
+
method: "GET",
|
|
424
|
+
url: "/confs/aggregations/tally"
|
|
425
|
+
});
|
|
426
|
+
return {
|
|
427
|
+
list: {
|
|
428
|
+
status: list.statusCode,
|
|
429
|
+
filter: listFilter
|
|
430
|
+
},
|
|
431
|
+
aggregation: {
|
|
432
|
+
status: aggregation.statusCode,
|
|
433
|
+
filter: aggFilter
|
|
434
|
+
},
|
|
435
|
+
close: () => app.close()
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
128
439
|
//#region src/testing/authSession.ts
|
|
129
440
|
function buildHeaders(token, orgId, extra) {
|
|
130
441
|
const headers = { authorization: `Bearer ${token}` };
|
|
@@ -845,214 +1156,6 @@ function createHttpTestHarness(resource, optionsOrGetter) {
|
|
|
845
1156
|
return new HttpTestHarness(resource, optionsOrGetter);
|
|
846
1157
|
}
|
|
847
1158
|
//#endregion
|
|
848
|
-
//#region src/testing/mocks.ts
|
|
849
|
-
/**
|
|
850
|
-
* Create a mock repository for testing
|
|
851
|
-
*
|
|
852
|
-
* @example
|
|
853
|
-
* const mockRepo = createMockRepository<Product>({
|
|
854
|
-
* getById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
|
|
855
|
-
* create: vi.fn().mockImplementation(data => Promise.resolve({ id: '1', ...data })),
|
|
856
|
-
* });
|
|
857
|
-
*
|
|
858
|
-
* await mockRepo.getById('1'); // Returns mocked product
|
|
859
|
-
*/
|
|
860
|
-
function createMockRepository(overrides = {}) {
|
|
861
|
-
return {
|
|
862
|
-
getAll: vi.fn().mockResolvedValue({
|
|
863
|
-
method: "offset",
|
|
864
|
-
data: [],
|
|
865
|
-
total: 0,
|
|
866
|
-
page: 1,
|
|
867
|
-
limit: 20,
|
|
868
|
-
pages: 0,
|
|
869
|
-
hasNext: false,
|
|
870
|
-
hasPrev: false
|
|
871
|
-
}),
|
|
872
|
-
getById: vi.fn().mockResolvedValue(null),
|
|
873
|
-
create: vi.fn().mockImplementation((data) => Promise.resolve({
|
|
874
|
-
_id: "mock-id",
|
|
875
|
-
...data
|
|
876
|
-
})),
|
|
877
|
-
update: vi.fn().mockImplementation((_id, data) => Promise.resolve({
|
|
878
|
-
_id: "mock-id",
|
|
879
|
-
...data
|
|
880
|
-
})),
|
|
881
|
-
delete: vi.fn().mockResolvedValue({
|
|
882
|
-
success: true,
|
|
883
|
-
message: "Deleted"
|
|
884
|
-
}),
|
|
885
|
-
updateMany: vi.fn().mockResolvedValue({
|
|
886
|
-
acknowledged: true,
|
|
887
|
-
matchedCount: 0,
|
|
888
|
-
modifiedCount: 0
|
|
889
|
-
}),
|
|
890
|
-
deleteMany: vi.fn().mockResolvedValue({
|
|
891
|
-
acknowledged: true,
|
|
892
|
-
deletedCount: 0
|
|
893
|
-
}),
|
|
894
|
-
claim: vi.fn().mockResolvedValue(null),
|
|
895
|
-
claimVersion: vi.fn().mockResolvedValue(null),
|
|
896
|
-
capabilities: {
|
|
897
|
-
transactions: false,
|
|
898
|
-
nestedTransactions: false,
|
|
899
|
-
upsert: false,
|
|
900
|
-
duplicateKeyError: false,
|
|
901
|
-
distinct: false,
|
|
902
|
-
aggregate: false,
|
|
903
|
-
getOrCreate: false,
|
|
904
|
-
countAndExists: false
|
|
905
|
-
},
|
|
906
|
-
getBySlug: vi.fn().mockResolvedValue(null),
|
|
907
|
-
getDeleted: vi.fn().mockResolvedValue([]),
|
|
908
|
-
restore: vi.fn().mockResolvedValue(null),
|
|
909
|
-
getTree: vi.fn().mockResolvedValue([]),
|
|
910
|
-
getChildren: vi.fn().mockResolvedValue([]),
|
|
911
|
-
...overrides
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
/**
|
|
915
|
-
* Create a mock user for authentication testing
|
|
916
|
-
*/
|
|
917
|
-
function createMockUser(overrides = {}) {
|
|
918
|
-
return {
|
|
919
|
-
_id: "mock-user-id",
|
|
920
|
-
id: "mock-user-id",
|
|
921
|
-
email: "test@example.com",
|
|
922
|
-
roles: ["user"],
|
|
923
|
-
organizationId: null,
|
|
924
|
-
...overrides
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Create a mock Fastify request
|
|
929
|
-
*/
|
|
930
|
-
function createMockRequest(overrides = {}) {
|
|
931
|
-
return {
|
|
932
|
-
body: {},
|
|
933
|
-
params: {},
|
|
934
|
-
query: {},
|
|
935
|
-
headers: {},
|
|
936
|
-
user: createMockUser(),
|
|
937
|
-
context: {},
|
|
938
|
-
log: {
|
|
939
|
-
info: vi.fn(),
|
|
940
|
-
warn: vi.fn(),
|
|
941
|
-
error: vi.fn(),
|
|
942
|
-
debug: vi.fn()
|
|
943
|
-
},
|
|
944
|
-
...overrides
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Create a mock Fastify reply
|
|
949
|
-
*/
|
|
950
|
-
function createMockReply() {
|
|
951
|
-
return {
|
|
952
|
-
code: vi.fn().mockReturnThis(),
|
|
953
|
-
send: vi.fn().mockReturnThis(),
|
|
954
|
-
header: vi.fn().mockReturnThis(),
|
|
955
|
-
headers: vi.fn().mockReturnThis(),
|
|
956
|
-
status: vi.fn().mockReturnThis(),
|
|
957
|
-
type: vi.fn().mockReturnThis(),
|
|
958
|
-
redirect: vi.fn().mockReturnThis(),
|
|
959
|
-
callNotFound: vi.fn().mockReturnThis(),
|
|
960
|
-
sent: false
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
/**
|
|
964
|
-
* Create a mock controller for testing
|
|
965
|
-
*/
|
|
966
|
-
function createMockController(repository) {
|
|
967
|
-
return {
|
|
968
|
-
repository,
|
|
969
|
-
list: vi.fn(),
|
|
970
|
-
get: vi.fn(),
|
|
971
|
-
create: vi.fn(),
|
|
972
|
-
update: vi.fn(),
|
|
973
|
-
delete: vi.fn()
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* Create mock data factory
|
|
978
|
-
*
|
|
979
|
-
* @example
|
|
980
|
-
* const productFactory = createDataFactory<Product>({
|
|
981
|
-
* name: () => faker.commerce.productName(),
|
|
982
|
-
* price: () => faker.number.int({ min: 10, max: 1000 }),
|
|
983
|
-
* sku: (i) => `SKU-${i}`,
|
|
984
|
-
* });
|
|
985
|
-
*
|
|
986
|
-
* const product = productFactory.build();
|
|
987
|
-
* const products = productFactory.buildMany(10);
|
|
988
|
-
*/
|
|
989
|
-
function createDataFactory(template) {
|
|
990
|
-
let counter = 0;
|
|
991
|
-
return {
|
|
992
|
-
build(overrides = {}) {
|
|
993
|
-
const index = counter++;
|
|
994
|
-
const data = {};
|
|
995
|
-
for (const [key, generator] of Object.entries(template)) data[key] = generator(index);
|
|
996
|
-
return {
|
|
997
|
-
...data,
|
|
998
|
-
...overrides
|
|
999
|
-
};
|
|
1000
|
-
},
|
|
1001
|
-
buildMany(count, overrides = {}) {
|
|
1002
|
-
return Array.from({ length: count }, () => this.build(overrides));
|
|
1003
|
-
},
|
|
1004
|
-
reset() {
|
|
1005
|
-
counter = 0;
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Create a spy that tracks function calls
|
|
1011
|
-
*
|
|
1012
|
-
* Useful for testing side effects without full mocking
|
|
1013
|
-
*/
|
|
1014
|
-
function createSpy(_name = "spy") {
|
|
1015
|
-
const calls = [];
|
|
1016
|
-
const spy = vi.fn((...args) => {
|
|
1017
|
-
calls.push(args);
|
|
1018
|
-
});
|
|
1019
|
-
spy.getCalls = () => calls;
|
|
1020
|
-
spy.getLastCall = () => calls[calls.length - 1] || [];
|
|
1021
|
-
return spy;
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Wait for a condition to be true
|
|
1025
|
-
*
|
|
1026
|
-
* Useful for async testing
|
|
1027
|
-
*/
|
|
1028
|
-
async function waitFor(condition, options = {}) {
|
|
1029
|
-
const { timeout = 5e3, interval = 100 } = options;
|
|
1030
|
-
const startTime = Date.now();
|
|
1031
|
-
while (Date.now() - startTime < timeout) {
|
|
1032
|
-
if (await condition()) return;
|
|
1033
|
-
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
1034
|
-
}
|
|
1035
|
-
throw new Error(`Timeout waiting for condition after ${timeout}ms`);
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Create a test timer that can be controlled
|
|
1039
|
-
*/
|
|
1040
|
-
function createTestTimer() {
|
|
1041
|
-
let time = Date.now();
|
|
1042
|
-
return {
|
|
1043
|
-
now: () => time,
|
|
1044
|
-
advance: (ms) => {
|
|
1045
|
-
time += ms;
|
|
1046
|
-
},
|
|
1047
|
-
set: (timestamp) => {
|
|
1048
|
-
time = timestamp;
|
|
1049
|
-
},
|
|
1050
|
-
reset: () => {
|
|
1051
|
-
time = Date.now();
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
//#endregion
|
|
1056
1159
|
//#region src/testing/testApp.ts
|
|
1057
1160
|
/**
|
|
1058
1161
|
* createTestApp — test app factory for arc
|
|
@@ -1118,7 +1221,7 @@ function pickDefaultAuth(authMode, callerAuth) {
|
|
|
1118
1221
|
};
|
|
1119
1222
|
}
|
|
1120
1223
|
async function createTestApp(options = {}) {
|
|
1121
|
-
const { createApp } = await import("../createApp-
|
|
1224
|
+
const { createApp } = await import("../createApp-CXrKFciH.mjs").then((n) => n.r);
|
|
1122
1225
|
const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
|
|
1123
1226
|
let dbHandle;
|
|
1124
1227
|
let dbUri;
|
|
@@ -1188,4 +1291,4 @@ function createMinimalTestApp(options = {}) {
|
|
|
1188
1291
|
});
|
|
1189
1292
|
}
|
|
1190
1293
|
//#endregion
|
|
1191
|
-
export { HttpTestHarness, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runStorageContract, safeParseBody, setupBetterAuthTestApp, waitFor };
|
|
1294
|
+
export { HttpTestHarness, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, setupBetterAuthTestApp, waitFor };
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as OpenApiSchemas, A as RequestIdOptions, At as RouteDefinition, Bt as RouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as AuthenticatorContext, H as CrudController, Ht as AuthHelpers, I as MiddlewareHandler, It as FastifyHandler, J as PresetHook, K as ResolvedTenantPurge, Kt as JwtContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthPluginOptions, W as ResourcePermissions, Wt as Authenticator, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kn as BaseControllerOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as TokenPair, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-
|
|
1
|
+
import { $ as OpenApiSchemas, A as RequestIdOptions, At as RouteDefinition, Bt as RouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as AuthenticatorContext, H as CrudController, Ht as AuthHelpers, I as MiddlewareHandler, It as FastifyHandler, J as PresetHook, K as ResolvedTenantPurge, Kt as JwtContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthPluginOptions, W as ResourcePermissions, Wt as Authenticator, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kn as BaseControllerOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as TokenPair, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-Q5gzEIWY.mjs";
|
|
2
2
|
import { i as RequestScope } from "../types-DjC-KUCz.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { a as UserBase, d as JWTPayload, f as ObjectId, i as PermissionContext, l as AnyRecord, m as UserOrganization, n as DataPolicy, p as UserLike, r as PermissionCheck, t as AuthorizationDecision, u as ArcRequest } from "../types-ChvvVNP8.mjs";
|
|
4
4
|
import { n as ElevationOptions, t as ElevationEvent } from "../elevation-C9s5hFP_.mjs";
|
|
5
|
-
export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext,
|
|
5
|
+
export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, DataPolicy, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { V as ResourceDefinition } from "./index-
|
|
1
|
+
import { V as ResourceDefinition } from "./index-Q5gzEIWY.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/integrations/mcp/types.d.ts
|
|
4
4
|
/** Behavioral hints for MCP clients (tool annotations per MCP spec) */
|