@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.
- package/README.md +187 -18
- package/bin/arc.js +11 -3
- package/dist/BaseController-CkM5dUh_.mjs +1031 -0
- package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
- package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
- package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
- package/dist/adapters/index.d.mts +3 -5
- package/dist/adapters/index.mjs +2 -3
- package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
- package/dist/audit/index.d.mts +4 -7
- package/dist/audit/index.mjs +2 -29
- package/dist/audit/mongodb.d.mts +1 -4
- package/dist/audit/mongodb.mjs +2 -3
- package/dist/auth/index.d.mts +7 -9
- package/dist/auth/index.mjs +65 -63
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/auth/redis-session.mjs +1 -2
- package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
- package/dist/cache/index.d.mts +23 -23
- package/dist/cache/index.mjs +4 -6
- package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
- package/dist/chunk-BpYLSNr0.mjs +14 -0
- package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
- package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
- package/dist/cli/commands/describe.mjs +24 -7
- package/dist/cli/commands/docs.mjs +6 -7
- package/dist/cli/commands/doctor.d.mts +10 -0
- package/dist/cli/commands/doctor.mjs +156 -0
- package/dist/cli/commands/generate.mjs +66 -17
- package/dist/cli/commands/init.mjs +315 -45
- package/dist/cli/commands/introspect.mjs +2 -4
- package/dist/cli/index.d.mts +1 -10
- package/dist/cli/index.mjs +4 -153
- package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
- package/dist/core/index.d.mts +3 -5
- package/dist/core/index.mjs +5 -4
- package/dist/core-C1XCMtqM.mjs +185 -0
- package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
- package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
- package/dist/discovery/index.mjs +37 -5
- package/dist/docs/index.d.mts +6 -9
- package/dist/docs/index.mjs +3 -21
- package/dist/dynamic/index.d.mts +93 -0
- package/dist/dynamic/index.mjs +122 -0
- package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
- package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
- package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
- package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
- package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
- package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
- package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
- package/dist/events/index.d.mts +72 -7
- package/dist/events/index.mjs +216 -4
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +19 -7
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +3 -4
- package/dist/factory/index.d.mts +23 -9
- package/dist/factory/index.mjs +48 -3
- package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
- package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
- package/dist/hooks/index.d.mts +1 -3
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +5 -5
- package/dist/idempotency/index.mjs +3 -7
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +4 -5
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +2 -5
- package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
- package/dist/index-Diqcm14c.d.mts +369 -0
- package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
- package/dist/index.d.mts +100 -105
- package/dist/index.mjs +85 -58
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +8 -4
- package/dist/integrations/index.d.mts +4 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs.d.mts +2 -2
- package/dist/integrations/jobs.mjs +63 -14
- package/dist/integrations/mcp/index.d.mts +219 -0
- package/dist/integrations/mcp/index.mjs +572 -0
- package/dist/integrations/mcp/testing.d.mts +53 -0
- package/dist/integrations/mcp/testing.mjs +104 -0
- package/dist/integrations/streamline.mjs +39 -19
- package/dist/integrations/webhooks.d.mts +56 -0
- package/dist/integrations/webhooks.mjs +139 -0
- package/dist/integrations/websocket-redis.d.mts +46 -0
- package/dist/integrations/websocket-redis.mjs +50 -0
- package/dist/integrations/websocket.d.mts +68 -2
- package/dist/integrations/websocket.mjs +96 -13
- package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
- package/dist/interface-DGmPxakH.d.mts +2213 -0
- package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
- package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
- package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
- package/dist/metrics-Csh4nsvv.mjs +224 -0
- package/dist/migrations/index.d.mts +113 -44
- package/dist/migrations/index.mjs +84 -102
- package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
- package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
- package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
- package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
- package/dist/org/index.d.mts +12 -14
- package/dist/org/index.mjs +92 -119
- package/dist/org/types.d.mts +2 -2
- package/dist/org/types.mjs +1 -1
- package/dist/permissions/index.d.mts +4 -278
- package/dist/permissions/index.mjs +4 -579
- package/dist/permissions-CA5zg0yK.mjs +751 -0
- package/dist/plugins/index.d.mts +104 -107
- package/dist/plugins/index.mjs +203 -313
- package/dist/plugins/response-cache.mjs +4 -69
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +24 -11
- package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
- package/dist/policies/index.d.mts +2 -2
- package/dist/policies/index.mjs +80 -83
- package/dist/presets/index.d.mts +26 -19
- package/dist/presets/index.mjs +2 -142
- package/dist/presets/multiTenant.d.mts +1 -4
- package/dist/presets/multiTenant.mjs +4 -6
- package/dist/presets-C9QXJV1u.mjs +422 -0
- package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
- package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
- package/dist/queryParser-CgCtsjti.mjs +352 -0
- package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
- package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
- package/dist/registry/index.d.mts +1 -4
- package/dist/registry/index.mjs +3 -4
- package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
- package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
- package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
- package/dist/rpc/index.d.mts +90 -0
- package/dist/rpc/index.mjs +248 -0
- package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
- package/dist/schemas/index.d.mts +30 -30
- package/dist/schemas/index.mjs +2 -4
- package/dist/scope/index.d.mts +13 -2
- package/dist/scope/index.mjs +18 -5
- package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
- package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
- package/dist/testing/index.d.mts +551 -567
- package/dist/testing/index.mjs +1744 -1799
- package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
- package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
- package/dist/types/index.d.mts +4 -946
- package/dist/types/index.mjs +2 -4
- package/dist/types-BJmgxNbF.d.mts +275 -0
- package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
- package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
- package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
- package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
- package/dist/utils/index.d.mts +254 -351
- package/dist/utils/index.mjs +7 -6
- package/dist/utils-Dc0WhlIl.mjs +594 -0
- package/dist/versioning-BzfeHmhj.mjs +37 -0
- package/package.json +44 -10
- package/skills/arc/SKILL.md +518 -0
- package/skills/arc/references/auth.md +250 -0
- package/skills/arc/references/events.md +272 -0
- package/skills/arc/references/integrations.md +385 -0
- package/skills/arc/references/mcp.md +431 -0
- package/skills/arc/references/production.md +610 -0
- package/skills/arc/references/testing.md +183 -0
- package/dist/audited-CGdLiSlE.mjs +0 -140
- package/dist/chunk-C7Uep-_p.mjs +0 -20
- package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
- package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
- package/dist/interface-BtdYtQUA.d.mts +0 -1114
- package/dist/presets-BTeYbw7h.d.mts +0 -57
- package/dist/presets-CeFtfDR8.mjs +0 -119
- /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
- /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
- /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,450 +1,9 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import {
|
|
3
|
-
import "
|
|
4
|
-
import {
|
|
5
|
-
import "
|
|
6
|
-
|
|
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/
|
|
476
|
+
//#region src/testing/mocks.d.ts
|
|
736
477
|
/**
|
|
737
|
-
*
|
|
478
|
+
* Extended repository interface for testing (includes optional preset methods)
|
|
738
479
|
*/
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
*
|
|
610
|
+
* Run all baseline tests
|
|
611
|
+
*
|
|
612
|
+
* Executes CRUD, validation, and preset tests
|
|
745
613
|
*/
|
|
746
|
-
|
|
614
|
+
runAll(): void;
|
|
747
615
|
/**
|
|
748
|
-
*
|
|
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
|
-
|
|
624
|
+
runCrud(): void;
|
|
751
625
|
/**
|
|
752
|
-
*
|
|
626
|
+
* Run validation tests
|
|
627
|
+
*
|
|
628
|
+
* Tests schema validation, required fields, etc.
|
|
753
629
|
*/
|
|
754
|
-
|
|
630
|
+
runValidation(): void;
|
|
755
631
|
/**
|
|
756
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
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
|
|
775
|
-
uri?: string;
|
|
776
|
-
dbName?: string;
|
|
777
|
-
}): void;
|
|
716
|
+
declare function generateTestFile(resourceName: string, options?: GenerateTestFileOptions): string;
|
|
778
717
|
/**
|
|
779
|
-
*
|
|
718
|
+
* Run config-level tests for a resource (no DB required)
|
|
780
719
|
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
720
|
+
* Tests field permissions, pipeline configuration, and event definitions.
|
|
721
|
+
* Works with any adapter — no Mongoose dependency.
|
|
783
722
|
*
|
|
784
|
-
*
|
|
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
|
-
*
|
|
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
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
744
|
+
useInMemoryDb?: boolean;
|
|
803
745
|
/**
|
|
804
|
-
*
|
|
746
|
+
* MongoDB connection URI (only used if useInMemoryDb is false)
|
|
805
747
|
*/
|
|
806
|
-
|
|
748
|
+
mongoUri?: string;
|
|
749
|
+
}
|
|
750
|
+
interface TestAppResult {
|
|
751
|
+
/** Fastify app instance */
|
|
752
|
+
app: FastifyInstance;
|
|
807
753
|
/**
|
|
808
|
-
*
|
|
754
|
+
* Cleanup function to close app and disconnect database
|
|
755
|
+
* Call this in afterAll() or afterEach()
|
|
809
756
|
*/
|
|
810
|
-
|
|
757
|
+
close: () => Promise<void>;
|
|
758
|
+
/** MongoDB connection URI (useful for connecting models) */
|
|
759
|
+
mongoUri?: string;
|
|
811
760
|
}
|
|
812
761
|
/**
|
|
813
|
-
*
|
|
762
|
+
* Create a test application instance with optional in-memory MongoDB
|
|
814
763
|
*
|
|
815
|
-
*
|
|
764
|
+
* **Performance Boost**: Uses in-memory MongoDB by default for 10x faster tests.
|
|
816
765
|
*
|
|
817
|
-
* @example
|
|
818
|
-
*
|
|
766
|
+
* @example Basic usage with in-memory DB
|
|
767
|
+
* ```typescript
|
|
768
|
+
* import { createTestApp } from '@classytic/arc/testing';
|
|
819
769
|
*
|
|
820
|
-
* describe('
|
|
821
|
-
*
|
|
770
|
+
* describe('API Tests', () => {
|
|
771
|
+
* let testApp: TestAppResult;
|
|
822
772
|
*
|
|
823
773
|
* beforeAll(async () => {
|
|
824
|
-
* await
|
|
774
|
+
* testApp = await createTestApp({
|
|
775
|
+
* auth: { type: 'jwt', jwt: { secret: 'test-secret' } },
|
|
776
|
+
* });
|
|
825
777
|
* });
|
|
826
778
|
*
|
|
827
779
|
* afterAll(async () => {
|
|
828
|
-
* await
|
|
780
|
+
* await testApp.close(); // Cleans up DB and disconnects
|
|
829
781
|
* });
|
|
830
782
|
*
|
|
831
|
-
* test('
|
|
832
|
-
* const
|
|
833
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
*
|
|
857
|
+
* Helper to create a test request builder
|
|
879
858
|
*/
|
|
880
|
-
declare
|
|
881
|
-
|
|
882
|
-
|
|
859
|
+
declare function request(app: FastifyInstance): TestRequestBuilder;
|
|
860
|
+
/**
|
|
861
|
+
* Test helper for authentication
|
|
862
|
+
*/
|
|
863
|
+
declare function createTestAuth(app: FastifyInstance): {
|
|
883
864
|
/**
|
|
884
|
-
*
|
|
865
|
+
* Generate a JWT token for testing
|
|
885
866
|
*/
|
|
886
|
-
|
|
867
|
+
generateToken(user: Record<string, unknown>): string;
|
|
887
868
|
/**
|
|
888
|
-
*
|
|
869
|
+
* Decode a JWT token
|
|
889
870
|
*/
|
|
890
|
-
|
|
871
|
+
decodeToken(token: string): Record<string, unknown> | null;
|
|
891
872
|
/**
|
|
892
|
-
*
|
|
873
|
+
* Verify a JWT token
|
|
893
874
|
*/
|
|
894
|
-
|
|
895
|
-
}
|
|
875
|
+
verifyToken(token: string): Promise<Record<string, unknown>>;
|
|
876
|
+
};
|
|
896
877
|
/**
|
|
897
|
-
*
|
|
878
|
+
* Snapshot testing helper for API responses
|
|
898
879
|
*/
|
|
899
|
-
declare
|
|
900
|
-
private snapshots;
|
|
901
|
-
private connection;
|
|
902
|
-
constructor(connection: Connection);
|
|
880
|
+
declare function createSnapshotMatcher(): {
|
|
903
881
|
/**
|
|
904
|
-
*
|
|
882
|
+
* Match response structure (ignores dynamic values like timestamps)
|
|
905
883
|
*/
|
|
906
|
-
|
|
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
|
-
*
|
|
892
|
+
* Load test data into database
|
|
909
893
|
*/
|
|
910
|
-
|
|
894
|
+
load(collection: string, items: Record<string, unknown>[]): Promise<Record<string, unknown>[]>;
|
|
911
895
|
/**
|
|
912
|
-
* Clear
|
|
896
|
+
* Clear all loaded test data
|
|
913
897
|
*/
|
|
914
|
-
|
|
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 };
|