@classytic/arc 2.3.0 → 2.4.2

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.
Files changed (175) hide show
  1. package/README.md +187 -18
  2. package/bin/arc.js +11 -3
  3. package/dist/BaseController-CkM5dUh_.mjs +1031 -0
  4. package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
  5. package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
  6. package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
  7. package/dist/adapters/index.d.mts +3 -5
  8. package/dist/adapters/index.mjs +2 -3
  9. package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
  10. package/dist/audit/index.d.mts +4 -7
  11. package/dist/audit/index.mjs +2 -29
  12. package/dist/audit/mongodb.d.mts +1 -4
  13. package/dist/audit/mongodb.mjs +2 -3
  14. package/dist/auth/index.d.mts +7 -9
  15. package/dist/auth/index.mjs +65 -63
  16. package/dist/auth/redis-session.d.mts +1 -1
  17. package/dist/auth/redis-session.mjs +1 -2
  18. package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
  19. package/dist/cache/index.d.mts +23 -23
  20. package/dist/cache/index.mjs +4 -6
  21. package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
  22. package/dist/chunk-BpYLSNr0.mjs +14 -0
  23. package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
  24. package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
  25. package/dist/cli/commands/describe.mjs +24 -7
  26. package/dist/cli/commands/docs.mjs +6 -7
  27. package/dist/cli/commands/doctor.d.mts +10 -0
  28. package/dist/cli/commands/doctor.mjs +156 -0
  29. package/dist/cli/commands/generate.mjs +66 -17
  30. package/dist/cli/commands/init.mjs +315 -45
  31. package/dist/cli/commands/introspect.mjs +2 -4
  32. package/dist/cli/index.d.mts +1 -10
  33. package/dist/cli/index.mjs +4 -153
  34. package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
  35. package/dist/core/index.d.mts +3 -5
  36. package/dist/core/index.mjs +5 -4
  37. package/dist/core-C1XCMtqM.mjs +185 -0
  38. package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
  39. package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
  40. package/dist/discovery/index.mjs +37 -5
  41. package/dist/docs/index.d.mts +6 -9
  42. package/dist/docs/index.mjs +3 -21
  43. package/dist/dynamic/index.d.mts +93 -0
  44. package/dist/dynamic/index.mjs +122 -0
  45. package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
  46. package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
  47. package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
  48. package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
  49. package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
  50. package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
  51. package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
  52. package/dist/events/index.d.mts +72 -7
  53. package/dist/events/index.mjs +216 -4
  54. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  55. package/dist/events/transports/redis-stream-entry.mjs +19 -7
  56. package/dist/events/transports/redis.d.mts +1 -1
  57. package/dist/events/transports/redis.mjs +3 -4
  58. package/dist/factory/index.d.mts +23 -9
  59. package/dist/factory/index.mjs +48 -3
  60. package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
  61. package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
  62. package/dist/hooks/index.d.mts +1 -3
  63. package/dist/hooks/index.mjs +2 -3
  64. package/dist/idempotency/index.d.mts +5 -5
  65. package/dist/idempotency/index.mjs +3 -7
  66. package/dist/idempotency/mongodb.d.mts +1 -1
  67. package/dist/idempotency/mongodb.mjs +4 -5
  68. package/dist/idempotency/redis.d.mts +1 -1
  69. package/dist/idempotency/redis.mjs +2 -5
  70. package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
  73. package/dist/index.d.mts +100 -105
  74. package/dist/index.mjs +85 -58
  75. package/dist/integrations/event-gateway.d.mts +1 -1
  76. package/dist/integrations/event-gateway.mjs +8 -4
  77. package/dist/integrations/index.d.mts +4 -2
  78. package/dist/integrations/index.mjs +1 -1
  79. package/dist/integrations/jobs.d.mts +2 -2
  80. package/dist/integrations/jobs.mjs +63 -14
  81. package/dist/integrations/mcp/index.d.mts +219 -0
  82. package/dist/integrations/mcp/index.mjs +572 -0
  83. package/dist/integrations/mcp/testing.d.mts +53 -0
  84. package/dist/integrations/mcp/testing.mjs +104 -0
  85. package/dist/integrations/streamline.mjs +39 -19
  86. package/dist/integrations/webhooks.d.mts +56 -0
  87. package/dist/integrations/webhooks.mjs +139 -0
  88. package/dist/integrations/websocket-redis.d.mts +46 -0
  89. package/dist/integrations/websocket-redis.mjs +50 -0
  90. package/dist/integrations/websocket.d.mts +68 -2
  91. package/dist/integrations/websocket.mjs +96 -13
  92. package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
  93. package/dist/interface-DGmPxakH.d.mts +2213 -0
  94. package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
  95. package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
  96. package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
  97. package/dist/metrics-Csh4nsvv.mjs +224 -0
  98. package/dist/migrations/index.d.mts +113 -44
  99. package/dist/migrations/index.mjs +84 -102
  100. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  101. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  102. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  103. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  104. package/dist/org/index.d.mts +12 -14
  105. package/dist/org/index.mjs +92 -119
  106. package/dist/org/types.d.mts +2 -2
  107. package/dist/org/types.mjs +1 -1
  108. package/dist/permissions/index.d.mts +4 -278
  109. package/dist/permissions/index.mjs +4 -579
  110. package/dist/permissions-CA5zg0yK.mjs +751 -0
  111. package/dist/plugins/index.d.mts +104 -107
  112. package/dist/plugins/index.mjs +203 -313
  113. package/dist/plugins/response-cache.mjs +4 -69
  114. package/dist/plugins/tracing-entry.d.mts +1 -1
  115. package/dist/plugins/tracing-entry.mjs +24 -11
  116. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  117. package/dist/policies/index.d.mts +2 -2
  118. package/dist/policies/index.mjs +80 -83
  119. package/dist/presets/index.d.mts +26 -19
  120. package/dist/presets/index.mjs +2 -142
  121. package/dist/presets/multiTenant.d.mts +1 -4
  122. package/dist/presets/multiTenant.mjs +4 -6
  123. package/dist/presets-C9QXJV1u.mjs +422 -0
  124. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  125. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  126. package/dist/queryParser-CgCtsjti.mjs +352 -0
  127. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  128. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  129. package/dist/registry/index.d.mts +1 -4
  130. package/dist/registry/index.mjs +3 -4
  131. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  132. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  133. package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
  134. package/dist/rpc/index.d.mts +90 -0
  135. package/dist/rpc/index.mjs +248 -0
  136. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  137. package/dist/schemas/index.d.mts +30 -30
  138. package/dist/schemas/index.mjs +2 -4
  139. package/dist/scope/index.d.mts +13 -2
  140. package/dist/scope/index.mjs +18 -5
  141. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  142. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  143. package/dist/testing/index.d.mts +551 -567
  144. package/dist/testing/index.mjs +1744 -1799
  145. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  146. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  147. package/dist/types/index.d.mts +4 -946
  148. package/dist/types/index.mjs +2 -4
  149. package/dist/types-BJmgxNbF.d.mts +275 -0
  150. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  151. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  152. package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
  153. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  154. package/dist/utils/index.d.mts +254 -351
  155. package/dist/utils/index.mjs +7 -6
  156. package/dist/utils-Dc0WhlIl.mjs +594 -0
  157. package/dist/versioning-BzfeHmhj.mjs +37 -0
  158. package/package.json +44 -10
  159. package/skills/arc/SKILL.md +518 -0
  160. package/skills/arc/references/auth.md +250 -0
  161. package/skills/arc/references/events.md +272 -0
  162. package/skills/arc/references/integrations.md +385 -0
  163. package/skills/arc/references/mcp.md +431 -0
  164. package/skills/arc/references/production.md +610 -0
  165. package/skills/arc/references/testing.md +183 -0
  166. package/dist/audited-CGdLiSlE.mjs +0 -140
  167. package/dist/chunk-C7Uep-_p.mjs +0 -20
  168. package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
  169. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  170. package/dist/interface-BtdYtQUA.d.mts +0 -1114
  171. package/dist/presets-BTeYbw7h.d.mts +0 -57
  172. package/dist/presets-CeFtfDR8.mjs +0 -119
  173. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  174. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  175. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
@@ -1,450 +1,9 @@
1
- import "../elevation-DGo5shaX.mjs";
2
- import { D as CrudRepository, T as ResourceDefinition } from "../interface-BtdYtQUA.mjs";
3
- import "../types-RLkFVgaw.mjs";
4
- import { AnyRecord } from "../types/index.mjs";
5
- import "../queryCachePlugin-Q6SYuHZ6.mjs";
6
- import "../eventPlugin-H6wDDjGO.mjs";
7
- import "../errorHandler-CW3OOeYq.mjs";
8
- import { r as CreateAppOptions } from "../types-tKwaViYB.mjs";
9
- import Fastify, { FastifyInstance } from "fastify";
10
- import { Mock } from "vitest";
11
- import { Connection } from "mongoose";
12
-
13
- //#region src/testing/TestHarness.d.ts
14
- /**
15
- * Test fixtures for a resource
16
- */
17
- interface TestFixtures$1<T = any> {
18
- /** Valid create payload */
19
- valid: Partial<T>;
20
- /** Update payload (optional, defaults to valid) */
21
- update?: Partial<T>;
22
- /** Invalid payload for validation tests (optional) */
23
- invalid?: Partial<T>;
24
- }
25
- /**
26
- * Test harness options
27
- */
28
- interface TestHarnessOptions<T = any> {
29
- /** Test data fixtures */
30
- fixtures: TestFixtures$1<T>;
31
- /** Custom setup function (runs before all tests) */
32
- setupFn?: () => Promise<void> | void;
33
- /** Custom teardown function (runs after all tests) */
34
- teardownFn?: () => Promise<void> | void;
35
- /** MongoDB connection URI (defaults to process.env.MONGO_URI) */
36
- mongoUri?: string;
37
- }
38
- /**
39
- * Test harness for Arc resources
40
- *
41
- * Provides automatic test generation for:
42
- * - CRUD operations (create, read, update, delete)
43
- * - Schema validation
44
- * - Preset-specific functionality (softDelete, slugLookup, tree, etc.)
45
- */
46
- declare class TestHarness<T = unknown> {
47
- private resource;
48
- private fixtures;
49
- private setupFn?;
50
- private teardownFn?;
51
- private mongoUri;
52
- private _createdIds;
53
- private Model;
54
- constructor(resource: ResourceDefinition<unknown>, options: TestHarnessOptions<T>);
55
- /**
56
- * Run all baseline tests
57
- *
58
- * Executes CRUD, validation, and preset tests
59
- */
60
- runAll(): void;
61
- /**
62
- * Run CRUD operation tests (model-level)
63
- *
64
- * Tests: create, read (list + getById), update, delete
65
- *
66
- * @deprecated Use `HttpTestHarness.runCrud()` for HTTP-level CRUD tests.
67
- * This method tests Mongoose models directly and does not exercise
68
- * HTTP routes, authentication, permissions, or the Arc pipeline.
69
- */
70
- runCrud(): void;
71
- /**
72
- * Run validation tests
73
- *
74
- * Tests schema validation, required fields, etc.
75
- */
76
- runValidation(): void;
77
- /**
78
- * Run preset-specific tests
79
- *
80
- * Auto-detects applied presets and tests their functionality:
81
- * - softDelete: deletedAt field, soft delete/restore
82
- * - slugLookup: slug generation
83
- * - tree: parent references, displayOrder
84
- * - multiTenant: organizationId requirement
85
- * - ownedByUser: userId requirement
86
- */
87
- runPresets(): void;
88
- /**
89
- * Run field-level permission tests
90
- *
91
- * Auto-generates tests for each field permission:
92
- * - hidden: field is stripped from responses
93
- * - visibleTo: field only shown to specified roles
94
- * - writableBy: field stripped from writes by non-privileged users
95
- * - redactFor: field shows redacted value for specified roles
96
- */
97
- runFieldPermissions(): void;
98
- /**
99
- * Run pipeline configuration tests
100
- *
101
- * Validates that pipeline steps are properly configured:
102
- * - All steps have names
103
- * - All steps have valid _type discriminants
104
- * - Operation filters (if set) use valid CRUD operation names
105
- */
106
- runPipeline(): void;
107
- /**
108
- * Run event definition tests
109
- *
110
- * Validates that events are properly defined:
111
- * - All events have handler functions
112
- * - Event names follow resource:action convention
113
- * - Schema definitions (if present) are valid objects
114
- */
115
- runEvents(): void;
116
- }
117
- /**
118
- * Create a test harness for an Arc resource
119
- *
120
- * @param resource - The Arc resource definition to test
121
- * @param options - Test harness configuration
122
- * @returns Test harness instance
123
- *
124
- * @example
125
- * import { createTestHarness } from '@classytic/arc/testing';
126
- *
127
- * const harness = createTestHarness(productResource, {
128
- * fixtures: {
129
- * valid: { name: 'Product', price: 100 },
130
- * update: { name: 'Updated' },
131
- * },
132
- * });
133
- *
134
- * harness.runAll(); // Generates 50+ baseline tests
135
- */
136
- declare function createTestHarness<T = any>(resource: ResourceDefinition, options: TestHarnessOptions<T>): TestHarness<T>;
137
- /**
138
- * Test file generation options
139
- */
140
- interface GenerateTestFileOptions {
141
- /** Applied presets (e.g., ['softDelete', 'slugLookup']) */
142
- presets?: string[];
143
- /** Module path for imports (default: '.') */
144
- modulePath?: string;
145
- }
146
- /**
147
- * Generate test file content for a resource
148
- *
149
- * Useful for scaffolding new resource tests via CLI
150
- *
151
- * @param resourceName - Resource name in kebab-case (e.g., 'product')
152
- * @param options - Generation options
153
- * @returns Complete test file content as string
154
- *
155
- * @example
156
- * const testContent = generateTestFile('product', {
157
- * presets: ['softDelete'],
158
- * modulePath: './modules/catalog',
159
- * });
160
- * fs.writeFileSync('product.test.js', testContent);
161
- */
162
- declare function generateTestFile(resourceName: string, options?: GenerateTestFileOptions): string;
163
- /**
164
- * Run config-level tests for a resource (no DB required)
165
- *
166
- * Tests field permissions, pipeline configuration, and event definitions.
167
- * Works with any adapter — no Mongoose dependency.
168
- *
169
- * @param resource - The Arc resource definition to test
170
- *
171
- * @example
172
- * ```typescript
173
- * import { createConfigTestSuite } from '@classytic/arc/testing';
174
- * import productResource from './product.resource.js';
175
- *
176
- * // Generates field permission, pipeline, and event tests
177
- * createConfigTestSuite(productResource);
178
- * ```
179
- */
180
- declare function createConfigTestSuite(resource: ResourceDefinition<unknown>): void;
181
- //#endregion
182
- //#region src/testing/testFactory.d.ts
183
- interface CreateTestAppOptions extends Partial<CreateAppOptions> {
184
- /**
185
- * Use in-memory MongoDB for faster tests (default: true)
186
- * Requires: mongodb-memory-server
187
- *
188
- * Set to false to use a provided mongoUri instead
189
- */
190
- useInMemoryDb?: boolean;
191
- /**
192
- * MongoDB connection URI (only used if useInMemoryDb is false)
193
- */
194
- mongoUri?: string;
195
- }
196
- interface TestAppResult {
197
- /** Fastify app instance */
198
- app: FastifyInstance;
199
- /**
200
- * Cleanup function to close app and disconnect database
201
- * Call this in afterAll() or afterEach()
202
- */
203
- close: () => Promise<void>;
204
- /** MongoDB connection URI (useful for connecting models) */
205
- mongoUri?: string;
206
- }
207
- /**
208
- * Create a test application instance with optional in-memory MongoDB
209
- *
210
- * **Performance Boost**: Uses in-memory MongoDB by default for 10x faster tests.
211
- *
212
- * @example Basic usage with in-memory DB
213
- * ```typescript
214
- * import { createTestApp } from '@classytic/arc/testing';
215
- *
216
- * describe('API Tests', () => {
217
- * let testApp: TestAppResult;
218
- *
219
- * beforeAll(async () => {
220
- * testApp = await createTestApp({
221
- * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },
222
- * });
223
- * });
224
- *
225
- * afterAll(async () => {
226
- * await testApp.close(); // Cleans up DB and disconnects
227
- * });
228
- *
229
- * test('GET /health', async () => {
230
- * const response = await testApp.app.inject({
231
- * method: 'GET',
232
- * url: '/health',
233
- * });
234
- * expect(response.statusCode).toBe(200);
235
- * });
236
- * });
237
- * ```
238
- *
239
- * @example Using external MongoDB
240
- * ```typescript
241
- * const testApp = await createTestApp({
242
- * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },
243
- * useInMemoryDb: false,
244
- * mongoUri: 'mongodb://localhost:27017/test-db',
245
- * });
246
- * ```
247
- *
248
- * @example Accessing MongoDB URI for model connections
249
- * ```typescript
250
- * const testApp = await createTestApp({
251
- * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },
252
- * });
253
- * await mongoose.connect(testApp.mongoUri); // Connect your models
254
- * ```
255
- */
256
- declare function createTestApp(options?: CreateTestAppOptions): Promise<TestAppResult>;
257
- /**
258
- * Create a minimal Fastify instance for unit tests
259
- *
260
- * Use when you don't need Arc's full plugin stack
261
- *
262
- * @example
263
- * const app = createMinimalTestApp();
264
- * app.get('/test', async () => ({ success: true }));
265
- *
266
- * const response = await app.inject({ method: 'GET', url: '/test' });
267
- * expect(response.json()).toEqual({ success: true });
268
- */
269
- declare function createMinimalTestApp(options?: Partial<any>): FastifyInstance;
270
- /**
271
- * Test request builder for cleaner tests
272
- *
273
- * @example
274
- * const request = new TestRequestBuilder(app)
275
- * .get('/products')
276
- * .withAuth(mockUser)
277
- * .withQuery({ page: 1, limit: 10 });
278
- *
279
- * const response = await request.send();
280
- * expect(response.statusCode).toBe(200);
281
- */
282
- declare class TestRequestBuilder {
283
- private method;
284
- private url;
285
- private body?;
286
- private query?;
287
- private headers;
288
- private app;
289
- constructor(app: FastifyInstance);
290
- get(url: string): this;
291
- post(url: string): this;
292
- put(url: string): this;
293
- patch(url: string): this;
294
- delete(url: string): this;
295
- withBody(body: any): this;
296
- withQuery(query: Record<string, any>): this;
297
- withHeader(key: string, value: string): this;
298
- withAuth(userOrHeaders: Record<string, unknown>): this;
299
- withContentType(type: string): this;
300
- send(): Promise<Fastify.LightMyRequestResponse>;
301
- }
302
- /**
303
- * Helper to create a test request builder
304
- */
305
- declare function request(app: FastifyInstance): TestRequestBuilder;
306
- /**
307
- * Test helper for authentication
308
- */
309
- declare function createTestAuth(app: FastifyInstance): {
310
- /**
311
- * Generate a JWT token for testing
312
- */
313
- generateToken(user: any): string;
314
- /**
315
- * Decode a JWT token
316
- */
317
- decodeToken(token: string): any;
318
- /**
319
- * Verify a JWT token
320
- */
321
- verifyToken(token: string): Promise<any>;
322
- };
323
- /**
324
- * Snapshot testing helper for API responses
325
- */
326
- declare function createSnapshotMatcher(): {
327
- /**
328
- * Match response structure (ignores dynamic values like timestamps)
329
- */
330
- matchStructure(response: any, expected: any): boolean;
331
- };
332
- /**
333
- * Bulk test data loader
334
- */
335
- declare class TestDataLoader {
336
- private data;
337
- private app;
338
- constructor(app: FastifyInstance);
339
- /**
340
- * Load test data into database
341
- */
342
- load(collection: string, items: any[]): Promise<any[]>;
343
- /**
344
- * Clear all loaded test data
345
- */
346
- cleanup(): Promise<void>;
347
- }
348
- //#endregion
349
- //#region src/testing/mocks.d.ts
350
- /**
351
- * Extended repository interface for testing (includes optional preset methods)
352
- */
353
- interface MockRepository<T> extends CrudRepository<T> {
354
- getBySlug?: Mock;
355
- getDeleted?: Mock;
356
- restore?: Mock;
357
- getTree?: Mock;
358
- getChildren?: Mock;
359
- [key: string]: unknown;
360
- }
361
- /**
362
- * Create a mock repository for testing
363
- *
364
- * @example
365
- * const mockRepo = createMockRepository<Product>({
366
- * getById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
367
- * create: vi.fn().mockImplementation(data => Promise.resolve({ id: '1', ...data })),
368
- * });
369
- *
370
- * await mockRepo.getById('1'); // Returns mocked product
371
- */
372
- declare function createMockRepository<T extends AnyRecord = AnyRecord>(overrides?: Partial<MockRepository<T>>): MockRepository<T>;
373
- /**
374
- * Create a mock user for authentication testing
375
- */
376
- declare function createMockUser(overrides?: Partial<AnyRecord>): {
377
- _id: string;
378
- id: string;
379
- email: string;
380
- roles: string[];
381
- organizationId: null;
382
- };
383
- /**
384
- * Create a mock Fastify request
385
- */
386
- declare function createMockRequest(overrides?: Partial<AnyRecord>): unknown;
387
- /**
388
- * Create a mock Fastify reply
389
- */
390
- declare function createMockReply(): unknown;
391
- /**
392
- * Create a mock controller for testing
393
- */
394
- declare function createMockController(repository: CrudRepository<AnyRecord>): {
395
- repository: CrudRepository<AnyRecord>;
396
- list: Mock<(...args: any[]) => any>;
397
- get: Mock<(...args: any[]) => any>;
398
- create: Mock<(...args: any[]) => any>;
399
- update: Mock<(...args: any[]) => any>;
400
- delete: Mock<(...args: any[]) => any>;
401
- };
402
- /**
403
- * Create mock data factory
404
- *
405
- * @example
406
- * const productFactory = createDataFactory<Product>({
407
- * name: () => faker.commerce.productName(),
408
- * price: () => faker.number.int({ min: 10, max: 1000 }),
409
- * sku: (i) => `SKU-${i}`,
410
- * });
411
- *
412
- * const product = productFactory.build();
413
- * const products = productFactory.buildMany(10);
414
- */
415
- declare function createDataFactory<T extends AnyRecord>(template: Record<keyof T, (index: number) => unknown>): {
416
- build(overrides?: Partial<T>): T;
417
- buildMany(count: number, overrides?: Partial<T>): T[];
418
- reset(): void;
419
- };
420
- /**
421
- * Create a spy that tracks function calls
422
- *
423
- * Useful for testing side effects without full mocking
424
- */
425
- declare function createSpy<T extends (...args: unknown[]) => unknown>(_name?: string): Mock<T> & {
426
- getCalls(): unknown[][];
427
- getLastCall(): unknown[];
428
- };
429
- /**
430
- * Wait for a condition to be true
431
- *
432
- * Useful for async testing
433
- */
434
- declare function waitFor(condition: () => boolean | Promise<boolean>, options?: {
435
- timeout?: number;
436
- interval?: number;
437
- }): Promise<void>;
438
- /**
439
- * Create a test timer that can be controlled
440
- */
441
- declare function createTestTimer(): {
442
- now: () => number;
443
- advance: (ms: number) => void;
444
- set: (timestamp: number) => void;
445
- reset: () => void;
446
- };
447
- //#endregion
1
+ import { Lt as ResourceDefinition, l as AnyRecord, zt as CrudRepository } from "../interface-DGmPxakH.mjs";
2
+ import { r as CreateAppOptions } from "../types-Dt0-AI6E.mjs";
3
+ import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
4
+ import { Connection } from "mongoose";
5
+ import { Mock } from "vitest";
6
+
448
7
  //#region src/testing/authHelpers.d.ts
449
8
  interface BetterAuthTestHelpersOptions {
450
9
  /** Base path for auth routes (default: '/api/auth') */
@@ -576,7 +135,189 @@ declare function createBetterAuthTestHelpers(options?: BetterAuthTestHelpersOpti
576
135
  * await ctx.teardown();
577
136
  * ```
578
137
  */
579
- declare function setupBetterAuthOrg(options: SetupBetterAuthOrgOptions): Promise<TestOrgContext>;
138
+ declare function setupBetterAuthOrg(options: SetupBetterAuthOrgOptions): Promise<TestOrgContext>;
139
+ //#endregion
140
+ //#region src/testing/dbHelpers.d.ts
141
+ /**
142
+ * Test database manager
143
+ */
144
+ declare class TestDatabase {
145
+ private connection?;
146
+ private dbName;
147
+ constructor(dbName?: string);
148
+ /**
149
+ * Connect to test database
150
+ */
151
+ connect(uri?: string): Promise<Connection>;
152
+ /**
153
+ * Disconnect and cleanup
154
+ */
155
+ disconnect(): Promise<void>;
156
+ /**
157
+ * Clear all collections
158
+ */
159
+ clear(): Promise<void>;
160
+ /**
161
+ * Get connection
162
+ */
163
+ getConnection(): Connection;
164
+ }
165
+ /**
166
+ * Higher-order function to wrap tests with database setup/teardown
167
+ *
168
+ * @example
169
+ * describe('Product Tests', () => {
170
+ * withTestDb(async (db) => {
171
+ * test('create product', async () => {
172
+ * const Product = db.getConnection().model('Product', schema);
173
+ * const product = await Product.create({ name: 'Test' });
174
+ * expect(product.name).toBe('Test');
175
+ * });
176
+ * });
177
+ * });
178
+ */
179
+ declare function withTestDb(tests: (db: TestDatabase) => void | Promise<void>, options?: {
180
+ uri?: string;
181
+ dbName?: string;
182
+ }): void;
183
+ /**
184
+ * Create test fixtures
185
+ *
186
+ * @example
187
+ * const fixtures = new TestFixtures(connection);
188
+ *
189
+ * await fixtures.load('products', [
190
+ * { name: 'Product 1', price: 100 },
191
+ * { name: 'Product 2', price: 200 },
192
+ * ]);
193
+ *
194
+ * const products = await fixtures.get('products');
195
+ */
196
+ declare class TestFixtures {
197
+ private fixtures;
198
+ private connection;
199
+ constructor(connection: Connection);
200
+ /**
201
+ * Load fixtures into a collection
202
+ */
203
+ load<T = any>(collectionName: string, data: Partial<T>[]): Promise<T[]>;
204
+ /**
205
+ * Get loaded fixtures
206
+ */
207
+ get<T = any>(collectionName: string): T[];
208
+ /**
209
+ * Get first fixture
210
+ */
211
+ getFirst<T = any>(collectionName: string): T | null;
212
+ /**
213
+ * Clear all fixtures
214
+ */
215
+ clear(): Promise<void>;
216
+ }
217
+ /**
218
+ * In-memory MongoDB for ultra-fast tests
219
+ *
220
+ * Requires: mongodb-memory-server
221
+ *
222
+ * @example
223
+ * import { InMemoryDatabase } from '@classytic/arc/testing';
224
+ *
225
+ * describe('Fast Tests', () => {
226
+ * const memoryDb = new InMemoryDatabase();
227
+ *
228
+ * beforeAll(async () => {
229
+ * await memoryDb.start();
230
+ * });
231
+ *
232
+ * afterAll(async () => {
233
+ * await memoryDb.stop();
234
+ * });
235
+ *
236
+ * test('create user', async () => {
237
+ * const uri = memoryDb.getUri();
238
+ * // Use uri for connection
239
+ * });
240
+ * });
241
+ */
242
+ declare class InMemoryDatabase {
243
+ private mongod?;
244
+ private uri?;
245
+ /**
246
+ * Start in-memory MongoDB
247
+ */
248
+ start(): Promise<string>;
249
+ /**
250
+ * Stop in-memory MongoDB
251
+ */
252
+ stop(): Promise<void>;
253
+ /**
254
+ * Get connection URI
255
+ */
256
+ getUri(): string;
257
+ }
258
+ /**
259
+ * Database transaction helper for testing
260
+ */
261
+ declare class TestTransaction {
262
+ private session?;
263
+ private connection;
264
+ constructor(connection: Connection);
265
+ /**
266
+ * Start transaction
267
+ */
268
+ start(): Promise<void>;
269
+ /**
270
+ * Commit transaction
271
+ */
272
+ commit(): Promise<void>;
273
+ /**
274
+ * Rollback transaction
275
+ */
276
+ rollback(): Promise<void>;
277
+ /**
278
+ * Get session
279
+ */
280
+ getSession(): any;
281
+ }
282
+ /**
283
+ * Seed data helper
284
+ */
285
+ declare class TestSeeder {
286
+ private connection;
287
+ constructor(connection: Connection);
288
+ /**
289
+ * Seed collection with data
290
+ */
291
+ seed<T>(collectionName: string, generator: () => T[], count?: number): Promise<T[]>;
292
+ /**
293
+ * Clear collection
294
+ */
295
+ clear(collectionName: string): Promise<void>;
296
+ /**
297
+ * Clear all collections
298
+ */
299
+ clearAll(): Promise<void>;
300
+ }
301
+ /**
302
+ * Database snapshot helper for rollback testing
303
+ */
304
+ declare class DatabaseSnapshot {
305
+ private snapshots;
306
+ private connection;
307
+ constructor(connection: Connection);
308
+ /**
309
+ * Take snapshot of current database state
310
+ */
311
+ take(): Promise<void>;
312
+ /**
313
+ * Restore database to snapshot
314
+ */
315
+ restore(): Promise<void>;
316
+ /**
317
+ * Clear snapshot
318
+ */
319
+ clear(): void;
320
+ }
580
321
  //#endregion
581
322
  //#region src/testing/HttpTestHarness.d.ts
582
323
  /**
@@ -732,186 +473,429 @@ declare class HttpTestHarness<T = unknown> {
732
473
  */
733
474
  declare function createHttpTestHarness<T = unknown>(resource: ResourceDefinition<unknown>, optionsOrGetter: HttpTestHarnessOptions<T> | (() => HttpTestHarnessOptions<T>)): HttpTestHarness<T>;
734
475
  //#endregion
735
- //#region src/testing/dbHelpers.d.ts
476
+ //#region src/testing/mocks.d.ts
736
477
  /**
737
- * Test database manager
478
+ * Extended repository interface for testing (includes optional preset methods)
738
479
  */
739
- declare class TestDatabase {
740
- private connection?;
741
- private dbName;
742
- constructor(dbName?: string);
480
+ interface MockRepository<T> extends CrudRepository<T> {
481
+ getBySlug?: Mock;
482
+ getDeleted?: Mock;
483
+ restore?: Mock;
484
+ getTree?: Mock;
485
+ getChildren?: Mock;
486
+ [key: string]: unknown;
487
+ }
488
+ /**
489
+ * Create a mock repository for testing
490
+ *
491
+ * @example
492
+ * const mockRepo = createMockRepository<Product>({
493
+ * getById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
494
+ * create: vi.fn().mockImplementation(data => Promise.resolve({ id: '1', ...data })),
495
+ * });
496
+ *
497
+ * await mockRepo.getById('1'); // Returns mocked product
498
+ */
499
+ declare function createMockRepository<T extends AnyRecord = AnyRecord>(overrides?: Partial<MockRepository<T>>): MockRepository<T>;
500
+ /**
501
+ * Create a mock user for authentication testing
502
+ */
503
+ declare function createMockUser(overrides?: Partial<AnyRecord>): {
504
+ _id: string;
505
+ id: string;
506
+ email: string;
507
+ roles: string[];
508
+ organizationId: null;
509
+ };
510
+ /**
511
+ * Create a mock Fastify request
512
+ */
513
+ declare function createMockRequest(overrides?: Partial<AnyRecord>): unknown;
514
+ /**
515
+ * Create a mock Fastify reply
516
+ */
517
+ declare function createMockReply(): unknown;
518
+ /**
519
+ * Create a mock controller for testing
520
+ */
521
+ declare function createMockController(repository: CrudRepository<AnyRecord>): {
522
+ repository: CrudRepository<AnyRecord>;
523
+ list: Mock<(...args: any[]) => any>;
524
+ get: Mock<(...args: any[]) => any>;
525
+ create: Mock<(...args: any[]) => any>;
526
+ update: Mock<(...args: any[]) => any>;
527
+ delete: Mock<(...args: any[]) => any>;
528
+ };
529
+ /**
530
+ * Create mock data factory
531
+ *
532
+ * @example
533
+ * const productFactory = createDataFactory<Product>({
534
+ * name: () => faker.commerce.productName(),
535
+ * price: () => faker.number.int({ min: 10, max: 1000 }),
536
+ * sku: (i) => `SKU-${i}`,
537
+ * });
538
+ *
539
+ * const product = productFactory.build();
540
+ * const products = productFactory.buildMany(10);
541
+ */
542
+ declare function createDataFactory<T extends AnyRecord>(template: Record<keyof T, (index: number) => unknown>): {
543
+ build(overrides?: Partial<T>): T;
544
+ buildMany(count: number, overrides?: Partial<T>): T[];
545
+ reset(): void;
546
+ };
547
+ /**
548
+ * Create a spy that tracks function calls
549
+ *
550
+ * Useful for testing side effects without full mocking
551
+ */
552
+ declare function createSpy<T extends (...args: unknown[]) => unknown>(_name?: string): Mock<T> & {
553
+ getCalls(): unknown[][];
554
+ getLastCall(): unknown[];
555
+ };
556
+ /**
557
+ * Wait for a condition to be true
558
+ *
559
+ * Useful for async testing
560
+ */
561
+ declare function waitFor(condition: () => boolean | Promise<boolean>, options?: {
562
+ timeout?: number;
563
+ interval?: number;
564
+ }): Promise<void>;
565
+ /**
566
+ * Create a test timer that can be controlled
567
+ */
568
+ declare function createTestTimer(): {
569
+ now: () => number;
570
+ advance: (ms: number) => void;
571
+ set: (timestamp: number) => void;
572
+ reset: () => void;
573
+ };
574
+ //#endregion
575
+ //#region src/testing/TestHarness.d.ts
576
+ /**
577
+ * Test fixtures for a resource
578
+ */
579
+ interface TestFixtures$1<T = any> {
580
+ /** Valid create payload */
581
+ valid: Partial<T>;
582
+ /** Update payload (optional, defaults to valid) */
583
+ update?: Partial<T>;
584
+ /** Invalid payload for validation tests (optional) */
585
+ invalid?: Partial<T>;
586
+ }
587
+ /**
588
+ * Test harness options
589
+ */
590
+ interface TestHarnessOptions<T = any> {
591
+ /** Test data fixtures */
592
+ fixtures: TestFixtures$1<T>;
593
+ /** Custom setup function (runs before all tests) */
594
+ setupFn?: () => Promise<void> | void;
595
+ /** Custom teardown function (runs after all tests) */
596
+ teardownFn?: () => Promise<void> | void;
597
+ /** MongoDB connection URI (defaults to process.env.MONGO_URI) */
598
+ mongoUri?: string;
599
+ }
600
+ declare class TestHarness<T = unknown> {
601
+ private resource;
602
+ private Model;
603
+ private fixtures;
604
+ private setupFn?;
605
+ private teardownFn?;
606
+ private mongoUri;
607
+ private _createdIds;
608
+ constructor(resource: ResourceDefinition<unknown>, options: TestHarnessOptions<T>);
743
609
  /**
744
- * Connect to test database
610
+ * Run all baseline tests
611
+ *
612
+ * Executes CRUD, validation, and preset tests
745
613
  */
746
- connect(uri?: string): Promise<Connection>;
614
+ runAll(): void;
747
615
  /**
748
- * Disconnect and cleanup
616
+ * Run CRUD operation tests (model-level)
617
+ *
618
+ * Tests: create, read (list + getById), update, delete
619
+ *
620
+ * @deprecated Use `HttpTestHarness.runCrud()` for HTTP-level CRUD tests.
621
+ * This method tests Mongoose models directly and does not exercise
622
+ * HTTP routes, authentication, permissions, or the Arc pipeline.
749
623
  */
750
- disconnect(): Promise<void>;
624
+ runCrud(): void;
751
625
  /**
752
- * Clear all collections
626
+ * Run validation tests
627
+ *
628
+ * Tests schema validation, required fields, etc.
753
629
  */
754
- clear(): Promise<void>;
630
+ runValidation(): void;
755
631
  /**
756
- * Get connection
632
+ * Run preset-specific tests
633
+ *
634
+ * Auto-detects applied presets and tests their functionality:
635
+ * - softDelete: deletedAt field, soft delete/restore
636
+ * - slugLookup: slug generation
637
+ * - tree: parent references, displayOrder
638
+ * - multiTenant: organizationId requirement
639
+ * - ownedByUser: userId requirement
757
640
  */
758
- getConnection(): Connection;
641
+ runPresets(): void;
642
+ /**
643
+ * Run field-level permission tests
644
+ *
645
+ * Auto-generates tests for each field permission:
646
+ * - hidden: field is stripped from responses
647
+ * - visibleTo: field only shown to specified roles
648
+ * - writableBy: field stripped from writes by non-privileged users
649
+ * - redactFor: field shows redacted value for specified roles
650
+ */
651
+ runFieldPermissions(): void;
652
+ /**
653
+ * Run pipeline configuration tests
654
+ *
655
+ * Validates that pipeline steps are properly configured:
656
+ * - All steps have names
657
+ * - All steps have valid _type discriminants
658
+ * - Operation filters (if set) use valid CRUD operation names
659
+ */
660
+ runPipeline(): void;
661
+ /**
662
+ * Run event definition tests
663
+ *
664
+ * Validates that events are properly defined:
665
+ * - All events have handler functions
666
+ * - Event names follow resource:action convention
667
+ * - Schema definitions (if present) are valid objects
668
+ */
669
+ runEvents(): void;
670
+ }
671
+ /**
672
+ * Create a test harness for an Arc resource
673
+ *
674
+ * @param resource - The Arc resource definition to test
675
+ * @param options - Test harness configuration
676
+ * @returns Test harness instance
677
+ *
678
+ * @example
679
+ * import { createTestHarness } from '@classytic/arc/testing';
680
+ *
681
+ * const harness = createTestHarness(productResource, {
682
+ * fixtures: {
683
+ * valid: { name: 'Product', price: 100 },
684
+ * update: { name: 'Updated' },
685
+ * },
686
+ * });
687
+ *
688
+ * harness.runAll(); // Generates 50+ baseline tests
689
+ */
690
+ declare function createTestHarness<T = any>(resource: ResourceDefinition, options: TestHarnessOptions<T>): TestHarness<T>;
691
+ /**
692
+ * Test file generation options
693
+ */
694
+ interface GenerateTestFileOptions {
695
+ /** Applied presets (e.g., ['softDelete', 'slugLookup']) */
696
+ presets?: string[];
697
+ /** Module path for imports (default: '.') */
698
+ modulePath?: string;
759
699
  }
760
700
  /**
761
- * Higher-order function to wrap tests with database setup/teardown
701
+ * Generate test file content for a resource
702
+ *
703
+ * Useful for scaffolding new resource tests via CLI
704
+ *
705
+ * @param resourceName - Resource name in kebab-case (e.g., 'product')
706
+ * @param options - Generation options
707
+ * @returns Complete test file content as string
762
708
  *
763
709
  * @example
764
- * describe('Product Tests', () => {
765
- * withTestDb(async (db) => {
766
- * test('create product', async () => {
767
- * const Product = db.getConnection().model('Product', schema);
768
- * const product = await Product.create({ name: 'Test' });
769
- * expect(product.name).toBe('Test');
770
- * });
771
- * });
710
+ * const testContent = generateTestFile('product', {
711
+ * presets: ['softDelete'],
712
+ * modulePath: './modules/catalog',
772
713
  * });
714
+ * fs.writeFileSync('product.test.js', testContent);
773
715
  */
774
- declare function withTestDb(tests: (db: TestDatabase) => void | Promise<void>, options?: {
775
- uri?: string;
776
- dbName?: string;
777
- }): void;
716
+ declare function generateTestFile(resourceName: string, options?: GenerateTestFileOptions): string;
778
717
  /**
779
- * Create test fixtures
718
+ * Run config-level tests for a resource (no DB required)
780
719
  *
781
- * @example
782
- * const fixtures = new TestFixtures(connection);
720
+ * Tests field permissions, pipeline configuration, and event definitions.
721
+ * Works with any adapter — no Mongoose dependency.
783
722
  *
784
- * await fixtures.load('products', [
785
- * { name: 'Product 1', price: 100 },
786
- * { name: 'Product 2', price: 200 },
787
- * ]);
723
+ * @param resource - The Arc resource definition to test
788
724
  *
789
- * const products = await fixtures.get('products');
725
+ * @example
726
+ * ```typescript
727
+ * import { createConfigTestSuite } from '@classytic/arc/testing';
728
+ * import productResource from './product.resource.js';
729
+ *
730
+ * // Generates field permission, pipeline, and event tests
731
+ * createConfigTestSuite(productResource);
732
+ * ```
790
733
  */
791
- declare class TestFixtures {
792
- private fixtures;
793
- private connection;
794
- constructor(connection: Connection);
795
- /**
796
- * Load fixtures into a collection
797
- */
798
- load<T = any>(collectionName: string, data: Partial<T>[]): Promise<T[]>;
734
+ declare function createConfigTestSuite(resource: ResourceDefinition<unknown>): void;
735
+ //#endregion
736
+ //#region src/testing/testFactory.d.ts
737
+ interface CreateTestAppOptions extends Partial<CreateAppOptions> {
799
738
  /**
800
- * Get loaded fixtures
739
+ * Use in-memory MongoDB for faster tests (default: true)
740
+ * Requires: mongodb-memory-server
741
+ *
742
+ * Set to false to use a provided mongoUri instead
801
743
  */
802
- get<T = any>(collectionName: string): T[];
744
+ useInMemoryDb?: boolean;
803
745
  /**
804
- * Get first fixture
746
+ * MongoDB connection URI (only used if useInMemoryDb is false)
805
747
  */
806
- getFirst<T = any>(collectionName: string): T | null;
748
+ mongoUri?: string;
749
+ }
750
+ interface TestAppResult {
751
+ /** Fastify app instance */
752
+ app: FastifyInstance;
807
753
  /**
808
- * Clear all fixtures
754
+ * Cleanup function to close app and disconnect database
755
+ * Call this in afterAll() or afterEach()
809
756
  */
810
- clear(): Promise<void>;
757
+ close: () => Promise<void>;
758
+ /** MongoDB connection URI (useful for connecting models) */
759
+ mongoUri?: string;
811
760
  }
812
761
  /**
813
- * In-memory MongoDB for ultra-fast tests
762
+ * Create a test application instance with optional in-memory MongoDB
814
763
  *
815
- * Requires: mongodb-memory-server
764
+ * **Performance Boost**: Uses in-memory MongoDB by default for 10x faster tests.
816
765
  *
817
- * @example
818
- * import { InMemoryDatabase } from '@classytic/arc/testing';
766
+ * @example Basic usage with in-memory DB
767
+ * ```typescript
768
+ * import { createTestApp } from '@classytic/arc/testing';
819
769
  *
820
- * describe('Fast Tests', () => {
821
- * const memoryDb = new InMemoryDatabase();
770
+ * describe('API Tests', () => {
771
+ * let testApp: TestAppResult;
822
772
  *
823
773
  * beforeAll(async () => {
824
- * await memoryDb.start();
774
+ * testApp = await createTestApp({
775
+ * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },
776
+ * });
825
777
  * });
826
778
  *
827
779
  * afterAll(async () => {
828
- * await memoryDb.stop();
780
+ * await testApp.close(); // Cleans up DB and disconnects
829
781
  * });
830
782
  *
831
- * test('create user', async () => {
832
- * const uri = memoryDb.getUri();
833
- * // Use uri for connection
783
+ * test('GET /health', async () => {
784
+ * const response = await testApp.app.inject({
785
+ * method: 'GET',
786
+ * url: '/health',
787
+ * });
788
+ * expect(response.statusCode).toBe(200);
834
789
  * });
835
790
  * });
791
+ * ```
792
+ *
793
+ * @example Using external MongoDB
794
+ * ```typescript
795
+ * const testApp = await createTestApp({
796
+ * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },
797
+ * useInMemoryDb: false,
798
+ * mongoUri: 'mongodb://localhost:27017/test-db',
799
+ * });
800
+ * ```
801
+ *
802
+ * @example Accessing MongoDB URI for model connections
803
+ * ```typescript
804
+ * const testApp = await createTestApp({
805
+ * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },
806
+ * });
807
+ * await mongoose.connect(testApp.mongoUri); // Connect your models
808
+ * ```
836
809
  */
837
- declare class InMemoryDatabase {
838
- private mongod?;
839
- private uri?;
840
- /**
841
- * Start in-memory MongoDB
842
- */
843
- start(): Promise<string>;
844
- /**
845
- * Stop in-memory MongoDB
846
- */
847
- stop(): Promise<void>;
848
- /**
849
- * Get connection URI
850
- */
851
- getUri(): string;
852
- }
810
+ declare function createTestApp(options?: CreateTestAppOptions): Promise<TestAppResult>;
853
811
  /**
854
- * Database transaction helper for testing
812
+ * Create a minimal Fastify instance for unit tests
813
+ *
814
+ * Use when you don't need Arc's full plugin stack
815
+ *
816
+ * @example
817
+ * const app = createMinimalTestApp();
818
+ * app.get('/test', async () => ({ success: true }));
819
+ *
820
+ * const response = await app.inject({ method: 'GET', url: '/test' });
821
+ * expect(response.json()).toEqual({ success: true });
855
822
  */
856
- declare class TestTransaction {
857
- private session?;
858
- private connection;
859
- constructor(connection: Connection);
860
- /**
861
- * Start transaction
862
- */
863
- start(): Promise<void>;
864
- /**
865
- * Commit transaction
866
- */
867
- commit(): Promise<void>;
868
- /**
869
- * Rollback transaction
870
- */
871
- rollback(): Promise<void>;
872
- /**
873
- * Get session
874
- */
875
- getSession(): any;
823
+ declare function createMinimalTestApp(options?: FastifyServerOptions): FastifyInstance;
824
+ /**
825
+ * Test request builder for cleaner tests
826
+ *
827
+ * @example
828
+ * const request = new TestRequestBuilder(app)
829
+ * .get('/products')
830
+ * .withAuth(mockUser)
831
+ * .withQuery({ page: 1, limit: 10 });
832
+ *
833
+ * const response = await request.send();
834
+ * expect(response.statusCode).toBe(200);
835
+ */
836
+ declare class TestRequestBuilder {
837
+ private method;
838
+ private url;
839
+ private body?;
840
+ private query?;
841
+ private headers;
842
+ private app;
843
+ constructor(app: FastifyInstance);
844
+ get(url: string): this;
845
+ post(url: string): this;
846
+ put(url: string): this;
847
+ patch(url: string): this;
848
+ delete(url: string): this;
849
+ withBody(body: Record<string, unknown>): this;
850
+ withQuery(query: Record<string, string | string[]>): this;
851
+ withHeader(key: string, value: string): this;
852
+ withAuth(userOrHeaders: Record<string, unknown>): this;
853
+ withContentType(type: string): this;
854
+ send(): Promise<Fastify.LightMyRequestResponse>;
876
855
  }
877
856
  /**
878
- * Seed data helper
857
+ * Helper to create a test request builder
879
858
  */
880
- declare class TestSeeder {
881
- private connection;
882
- constructor(connection: Connection);
859
+ declare function request(app: FastifyInstance): TestRequestBuilder;
860
+ /**
861
+ * Test helper for authentication
862
+ */
863
+ declare function createTestAuth(app: FastifyInstance): {
883
864
  /**
884
- * Seed collection with data
865
+ * Generate a JWT token for testing
885
866
  */
886
- seed<T>(collectionName: string, generator: () => T[], count?: number): Promise<T[]>;
867
+ generateToken(user: Record<string, unknown>): string;
887
868
  /**
888
- * Clear collection
869
+ * Decode a JWT token
889
870
  */
890
- clear(collectionName: string): Promise<void>;
871
+ decodeToken(token: string): Record<string, unknown> | null;
891
872
  /**
892
- * Clear all collections
873
+ * Verify a JWT token
893
874
  */
894
- clearAll(): Promise<void>;
895
- }
875
+ verifyToken(token: string): Promise<Record<string, unknown>>;
876
+ };
896
877
  /**
897
- * Database snapshot helper for rollback testing
878
+ * Snapshot testing helper for API responses
898
879
  */
899
- declare class DatabaseSnapshot {
900
- private snapshots;
901
- private connection;
902
- constructor(connection: Connection);
880
+ declare function createSnapshotMatcher(): {
903
881
  /**
904
- * Take snapshot of current database state
882
+ * Match response structure (ignores dynamic values like timestamps)
905
883
  */
906
- take(): Promise<void>;
884
+ matchStructure(response: unknown, expected: unknown): boolean;
885
+ };
886
+ /**
887
+ * Bulk test data loader
888
+ */
889
+ declare class TestDataLoader {
890
+ private data;
907
891
  /**
908
- * Restore database to snapshot
892
+ * Load test data into database
909
893
  */
910
- restore(): Promise<void>;
894
+ load(collection: string, items: Record<string, unknown>[]): Promise<Record<string, unknown>[]>;
911
895
  /**
912
- * Clear snapshot
896
+ * Clear all loaded test data
913
897
  */
914
- clear(): void;
898
+ cleanup(): Promise<void>;
915
899
  }
916
900
  //#endregion
917
901
  export { type AuthProvider, type AuthResponse, type BetterAuthTestHelpers, type BetterAuthTestHelpersOptions, type CreateTestAppOptions, DatabaseSnapshot, TestFixtures as DbTestFixtures, type GenerateTestFileOptions, HttpTestHarness, type HttpTestHarnessOptions, InMemoryDatabase, type OrgResponse, type SetupBetterAuthOrgOptions, type SetupUserConfig, type TestAppResult, TestDataLoader, TestDatabase, type TestFixtures$1 as TestFixtures, TestHarness, type TestHarnessOptions, type TestOrgContext, TestRequestBuilder, TestSeeder, TestTransaction, type TestUserContext, createBetterAuthProvider, createBetterAuthTestHelpers, createConfigTestSuite, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSnapshotMatcher, createSpy, createTestApp, createTestAuth, createTestHarness, createTestTimer, generateTestFile, request, safeParseBody, setupBetterAuthOrg, waitFor, withTestDb };