@classytic/arc 1.1.0 → 2.1.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 +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
- package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
- package/dist/HookSystem-BsGV-j2l.mjs +405 -0
- package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +82 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +276 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-C3T5DTUx.mjs +141 -0
- package/dist/audited-C3T5DTUx.mjs.map +1 -0
- package/dist/auth/index.d.mts +189 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +1102 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth/redis-session.d.mts +44 -0
- package/dist/auth/redis-session.d.mts.map +1 -0
- package/dist/auth/redis-session.mjs +76 -0
- package/dist/auth/redis-session.mjs.map +1 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
- package/dist/cache/index.d.mts +146 -0
- package/dist/cache/index.d.mts.map +1 -0
- package/dist/cache/index.mjs +92 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/caching-Bl28lYsR.mjs +94 -0
- package/dist/caching-Bl28lYsR.mjs.map +1 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
- package/dist/cli/commands/describe.d.mts +19 -0
- package/dist/cli/commands/describe.d.mts.map +1 -0
- package/dist/cli/commands/describe.mjs +239 -0
- package/dist/cli/commands/describe.mjs.map +1 -0
- package/dist/cli/commands/docs.d.mts +14 -0
- package/dist/cli/commands/docs.d.mts.map +1 -0
- package/dist/cli/commands/docs.mjs +53 -0
- package/dist/cli/commands/docs.mjs.map +1 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
- package/dist/cli/commands/generate.d.mts.map +1 -0
- package/dist/cli/commands/generate.mjs +358 -0
- package/dist/cli/commands/generate.mjs.map +1 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
- package/dist/cli/commands/init.d.mts.map +1 -0
- package/dist/cli/commands/{init.js → init.mjs} +807 -616
- package/dist/cli/commands/init.mjs.map +1 -0
- package/dist/cli/commands/introspect.d.mts +11 -0
- package/dist/cli/commands/introspect.d.mts.map +1 -0
- package/dist/cli/commands/introspect.mjs +76 -0
- package/dist/cli/commands/introspect.mjs.map +1 -0
- package/dist/cli/index.d.mts +17 -0
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/index.mjs +157 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/constants-DdXFXQtN.mjs +85 -0
- package/dist/constants-DdXFXQtN.mjs.map +1 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-CUgNqegw.mjs +560 -0
- package/dist/createApp-CUgNqegw.mjs.map +1 -0
- package/dist/defineResource-k0_BDn8v.mjs +2197 -0
- package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
- package/dist/discovery/index.d.mts +47 -0
- package/dist/discovery/index.d.mts.map +1 -0
- package/dist/discovery/index.mjs +110 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/docs/index.d.mts +163 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +73 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/elevation-BRy3yFWT.mjs +113 -0
- package/dist/elevation-BRy3yFWT.mjs.map +1 -0
- package/dist/elevation-B_2dRLVP.d.mts +88 -0
- package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
- package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
- package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
- package/dist/errorHandler-C1okiriz.mjs +109 -0
- package/dist/errorHandler-C1okiriz.mjs.map +1 -0
- package/dist/errors-B9bZok84.mjs +212 -0
- package/dist/errors-B9bZok84.mjs.map +1 -0
- package/dist/errors-ChKiFz62.d.mts +125 -0
- package/dist/errors-ChKiFz62.d.mts.map +1 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
- package/dist/eventPlugin-DGR_B2on.mjs +230 -0
- package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
- package/dist/events/index.d.mts +54 -0
- package/dist/events/index.d.mts.map +1 -0
- package/dist/events/index.mjs +52 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +178 -0
- package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
- package/dist/events/transports/redis.d.mts +77 -0
- package/dist/events/transports/redis.d.mts.map +1 -0
- package/dist/events/transports/redis.mjs +125 -0
- package/dist/events/transports/redis.mjs.map +1 -0
- package/dist/externalPaths-DlINfKbP.d.mts +51 -0
- package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
- package/dist/factory/index.d.mts +64 -0
- package/dist/factory/index.d.mts.map +1 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
- package/dist/fields-DyaDVX4J.d.mts +110 -0
- package/dist/fields-DyaDVX4J.d.mts.map +1 -0
- package/dist/fields-iagOozy0.mjs +115 -0
- package/dist/fields-iagOozy0.mjs.map +1 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +97 -0
- package/dist/idempotency/index.d.mts.map +1 -0
- package/dist/idempotency/index.mjs +320 -0
- package/dist/idempotency/index.mjs.map +1 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +115 -0
- package/dist/idempotency/mongodb.mjs.map +1 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +104 -0
- package/dist/idempotency/redis.mjs.map +1 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +105 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/event-gateway.d.mts +47 -0
- package/dist/integrations/event-gateway.d.mts.map +1 -0
- package/dist/integrations/event-gateway.mjs +44 -0
- package/dist/integrations/event-gateway.mjs.map +1 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +104 -0
- package/dist/integrations/jobs.d.mts.map +1 -0
- package/dist/integrations/jobs.mjs +124 -0
- package/dist/integrations/jobs.mjs.map +1 -0
- package/dist/integrations/streamline.d.mts +61 -0
- package/dist/integrations/streamline.d.mts.map +1 -0
- package/dist/integrations/streamline.mjs +126 -0
- package/dist/integrations/streamline.mjs.map +1 -0
- package/dist/integrations/websocket.d.mts +83 -0
- package/dist/integrations/websocket.d.mts.map +1 -0
- package/dist/integrations/websocket.mjs +289 -0
- package/dist/integrations/websocket.mjs.map +1 -0
- package/dist/interface-B01JvPVc.d.mts +78 -0
- package/dist/interface-B01JvPVc.d.mts.map +1 -0
- package/dist/interface-CZe8IkMf.d.mts +55 -0
- package/dist/interface-CZe8IkMf.d.mts.map +1 -0
- package/dist/interface-Ch8HU9uM.d.mts +1098 -0
- package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
- package/dist/keys-BqNejWup.mjs +43 -0
- package/dist/keys-BqNejWup.mjs.map +1 -0
- package/dist/logger-Df2O2WsW.mjs +79 -0
- package/dist/logger-Df2O2WsW.mjs.map +1 -0
- package/dist/memory-cQgelFOj.mjs +144 -0
- package/dist/memory-cQgelFOj.mjs.map +1 -0
- package/dist/migrations/index.d.mts +157 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +261 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/mongodb-BfJVlUJH.mjs +94 -0
- package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
- package/dist/mongodb-CGzRbfAK.d.mts +119 -0
- package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
- package/dist/mongodb-JN-9JA7K.d.mts +72 -0
- package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
- package/dist/openapi-G3Cw7XuM.mjs +524 -0
- package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
- package/dist/org/index.d.mts +69 -0
- package/dist/org/index.d.mts.map +1 -0
- package/dist/org/index.mjs +514 -0
- package/dist/org/index.mjs.map +1 -0
- package/dist/org/types.d.mts +83 -0
- package/dist/org/types.d.mts.map +1 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +279 -0
- package/dist/permissions/index.d.mts.map +1 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +173 -0
- package/dist/plugins/index.d.mts.map +1 -0
- package/dist/plugins/index.mjs +523 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/plugins/response-cache.d.mts +88 -0
- package/dist/plugins/response-cache.d.mts.map +1 -0
- package/dist/plugins/response-cache.mjs +284 -0
- package/dist/plugins/response-cache.mjs.map +1 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +186 -0
- package/dist/plugins/tracing-entry.mjs.map +1 -0
- package/dist/pluralize-CEweyOEm.mjs +87 -0
- package/dist/pluralize-CEweyOEm.mjs.map +1 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -169
- package/dist/policies/index.d.mts.map +1 -0
- package/dist/policies/index.mjs +322 -0
- package/dist/policies/index.mjs.map +1 -0
- package/dist/presets/{index.d.ts → index.d.mts} +63 -131
- package/dist/presets/index.d.mts.map +1 -0
- package/dist/presets/index.mjs +144 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/presets/multiTenant.d.mts +25 -0
- package/dist/presets/multiTenant.d.mts.map +1 -0
- package/dist/presets/multiTenant.mjs +114 -0
- package/dist/presets/multiTenant.mjs.map +1 -0
- package/dist/presets-BITljm96.mjs +120 -0
- package/dist/presets-BITljm96.mjs.map +1 -0
- package/dist/presets-DzSMwlKj.d.mts +58 -0
- package/dist/presets-DzSMwlKj.d.mts.map +1 -0
- package/dist/prisma-DJbMt3yf.mjs +628 -0
- package/dist/prisma-DJbMt3yf.mjs.map +1 -0
- package/dist/prisma-Dg9GoVdj.d.mts +275 -0
- package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
- package/dist/redis-D-JAeLtm.d.mts +50 -0
- package/dist/redis-D-JAeLtm.d.mts.map +1 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
- package/dist/registry/index.d.mts +12 -0
- package/dist/registry/index.d.mts.map +1 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-QQD6ROJc.mjs +56 -0
- package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
- package/dist/schemaConverter-BwrmWroW.mjs +99 -0
- package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
- package/dist/schemas/index.d.mts +64 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +83 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/scope/index.d.mts +22 -0
- package/dist/scope/index.d.mts.map +1 -0
- package/dist/scope/index.mjs +66 -0
- package/dist/scope/index.mjs.map +1 -0
- package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
- package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
- package/dist/sse-B3c3_yZp.mjs +124 -0
- package/dist/sse-B3c3_yZp.mjs.map +1 -0
- package/dist/testing/index.d.mts +908 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +1977 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/tracing-Cc7vVQPp.d.mts +71 -0
- package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
- package/dist/typeGuards-DhMNLuvU.mjs +10 -0
- package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
- package/dist/types/index.d.mts +947 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +15 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-Beqn1Un7.mjs +39 -0
- package/dist/types-Beqn1Un7.mjs.map +1 -0
- package/dist/types-CIgB7UUl.d.mts +446 -0
- package/dist/types-CIgB7UUl.d.mts.map +1 -0
- package/dist/types-aYB4V7uN.d.mts +87 -0
- package/dist/types-aYB4V7uN.d.mts.map +1 -0
- package/dist/utils/index.d.mts +748 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/testing/TestHarness.ts","../../src/testing/dbHelpers.ts","../../src/testing/testFactory.ts","../../src/testing/mocks.ts","../../src/testing/authHelpers.ts","../../src/testing/HttpTestHarness.ts"],"sourcesContent":["/**\n * Resource Test Harness\n *\n * Generates baseline tests for Arc resources automatically.\n * Tests CRUD operations + preset routes with minimal configuration.\n *\n * @example\n * import { createTestHarness } from '@classytic/arc/testing';\n * import productResource from './product.resource.js';\n *\n * const harness = createTestHarness(productResource, {\n * fixtures: {\n * valid: { name: 'Test Product', price: 100 },\n * update: { name: 'Updated Product' },\n * },\n * });\n *\n * // Run all baseline tests (50+ auto-generated)\n * harness.runAll();\n *\n * // Or run specific test suites\n * harness.runCrud();\n * harness.runPresets();\n */\n\nimport { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';\nimport mongoose, { Model, Document } from 'mongoose';\nimport type { ResourceDefinition } from '../core/defineResource.js';\nimport { applyFieldReadPermissions, applyFieldWritePermissions } from '../permissions/fields.js';\nimport type { FieldPermissionMap } from '../permissions/fields.js';\nimport type { PipelineConfig, PipelineStep } from '../pipeline/types.js';\nimport { CRUD_OPERATIONS } from '../constants.js';\n\n/**\n * Test fixtures for a resource\n */\nexport interface TestFixtures<T = any> {\n /** Valid create payload */\n valid: Partial<T>;\n /** Update payload (optional, defaults to valid) */\n update?: Partial<T>;\n /** Invalid payload for validation tests (optional) */\n invalid?: Partial<T>;\n}\n\n/**\n * Test harness options\n */\nexport interface TestHarnessOptions<T = any> {\n /** Test data fixtures */\n fixtures: TestFixtures<T>;\n /** Custom setup function (runs before all tests) */\n setupFn?: () => Promise<void> | void;\n /** Custom teardown function (runs after all tests) */\n teardownFn?: () => Promise<void> | void;\n /** MongoDB connection URI (defaults to process.env.MONGO_URI) */\n mongoUri?: string;\n}\n\n/**\n * Test harness for Arc resources\n *\n * Provides automatic test generation for:\n * - CRUD operations (create, read, update, delete)\n * - Schema validation\n * - Preset-specific functionality (softDelete, slugLookup, tree, etc.)\n */\nexport class TestHarness<T = unknown> {\n private resource: ResourceDefinition<unknown>;\n private fixtures: TestFixtures<T>;\n private setupFn?: () => Promise<void> | void;\n private teardownFn?: () => Promise<void> | void;\n private mongoUri: string;\n private _createdIds: any[] = [];\n private Model: Model<any>;\n\n constructor(resource: ResourceDefinition<unknown>, options: TestHarnessOptions<T>) {\n this.resource = resource;\n this.fixtures = options.fixtures;\n this.setupFn = options.setupFn;\n this.teardownFn = options.teardownFn;\n this.mongoUri = options.mongoUri || process.env.MONGO_URI || 'mongodb://localhost:27017/test';\n\n // Extract model from adapter (Mongoose only)\n if (!resource.adapter) {\n throw new Error(`TestHarness requires a resource with a database adapter`);\n }\n\n if (resource.adapter.type !== 'mongoose') {\n throw new Error(`TestHarness currently only supports Mongoose adapters`);\n }\n\n const model = (resource.adapter as { model?: Model<unknown> }).model;\n if (!model) {\n throw new Error(`Mongoose adapter for ${resource.name} does not have a model`);\n }\n\n this.Model = model as Model<unknown>;\n }\n\n /**\n * Run all baseline tests\n *\n * Executes CRUD, validation, and preset tests\n */\n runAll(): void {\n this.runCrud();\n this.runValidation();\n this.runPresets();\n this.runFieldPermissions();\n this.runPipeline();\n this.runEvents();\n }\n\n /**\n * Run CRUD operation tests (model-level)\n *\n * Tests: create, read (list + getById), update, delete\n *\n * @deprecated Use `HttpTestHarness.runCrud()` for HTTP-level CRUD tests.\n * This method tests Mongoose models directly and does not exercise\n * HTTP routes, authentication, permissions, or the Arc pipeline.\n */\n runCrud(): void {\n const { resource, fixtures, Model } = this;\n\n describe(`${resource.displayName} CRUD Operations`, () => {\n beforeAll(async () => {\n await mongoose.connect(this.mongoUri);\n if (this.setupFn) await this.setupFn();\n });\n\n afterAll(async () => {\n // Cleanup created documents\n if (this._createdIds.length > 0) {\n await Model.deleteMany({ _id: { $in: this._createdIds } });\n }\n if (this.teardownFn) await this.teardownFn();\n await mongoose.disconnect();\n });\n\n describe('Create', () => {\n it('should create a new document with valid data', async () => {\n const doc = await Model.create(fixtures.valid);\n this._createdIds.push(doc._id);\n\n expect(doc).toBeDefined();\n expect(doc._id).toBeDefined();\n\n // Verify all provided fields\n for (const [key, value] of Object.entries(fixtures.valid)) {\n if (typeof value !== 'object') {\n expect(doc[key]).toEqual(value);\n }\n }\n });\n\n it('should have timestamps', async () => {\n const doc = await Model.findById(this._createdIds[0]);\n expect(doc).toBeDefined();\n expect(doc!.createdAt).toBeDefined();\n expect(doc!.updatedAt).toBeDefined();\n });\n });\n\n describe('Read', () => {\n it('should find document by ID', async () => {\n const doc = await Model.findById(this._createdIds[0]);\n expect(doc).toBeDefined();\n });\n\n it('should list documents', async () => {\n const docs = await Model.find({});\n expect(Array.isArray(docs)).toBe(true);\n expect(docs.length).toBeGreaterThan(0);\n });\n });\n\n describe('Update', () => {\n it('should update document', async () => {\n const updateData = fixtures.update || { updatedAt: new Date() };\n const doc = await Model.findByIdAndUpdate(this._createdIds[0], updateData, {\n new: true,\n });\n expect(doc).toBeDefined();\n });\n });\n\n describe('Delete', () => {\n it('should delete document', async () => {\n // Create a doc specifically for deletion\n const toDelete = await Model.create(fixtures.valid);\n await Model.findByIdAndDelete(toDelete._id);\n const deleted = await Model.findById(toDelete._id);\n expect(deleted).toBeNull();\n });\n });\n });\n }\n\n /**\n * Run validation tests\n *\n * Tests schema validation, required fields, etc.\n */\n runValidation(): void {\n const { resource, fixtures, Model } = this;\n\n describe(`${resource.displayName} Validation`, () => {\n beforeAll(async () => {\n await mongoose.connect(this.mongoUri);\n });\n\n afterAll(async () => {\n await mongoose.disconnect();\n });\n\n it('should reject empty document', async () => {\n await expect(Model.create({})).rejects.toThrow();\n });\n\n if (fixtures.invalid) {\n it('should reject invalid data', async () => {\n await expect(Model.create(fixtures.invalid!)).rejects.toThrow();\n });\n }\n });\n }\n\n /**\n * Run preset-specific tests\n *\n * Auto-detects applied presets and tests their functionality:\n * - softDelete: deletedAt field, soft delete/restore\n * - slugLookup: slug generation\n * - tree: parent references, displayOrder\n * - multiTenant: organizationId requirement\n * - ownedByUser: userId requirement\n */\n runPresets(): void {\n const { resource, fixtures, Model } = this;\n const presets = (resource as any)._appliedPresets || [];\n\n if (presets.length === 0) return;\n\n describe(`${resource.displayName} Preset Tests`, () => {\n beforeAll(async () => {\n await mongoose.connect(this.mongoUri);\n });\n\n afterAll(async () => {\n await mongoose.disconnect();\n });\n\n // Soft Delete preset tests\n if (presets.includes('softDelete')) {\n describe('Soft Delete', () => {\n let testDoc: any;\n\n beforeEach(async () => {\n testDoc = await Model.create(fixtures.valid);\n this._createdIds.push(testDoc._id);\n });\n\n it('should have deletedAt field', () => {\n expect(testDoc.deletedAt).toBeDefined();\n expect(testDoc.deletedAt).toBeNull();\n });\n\n it('should soft delete (set deletedAt)', async () => {\n await Model.findByIdAndUpdate(testDoc._id, { deletedAt: new Date() });\n const deleted = await Model.findById(testDoc._id);\n expect(deleted!.deletedAt).not.toBeNull();\n });\n\n it('should restore (clear deletedAt)', async () => {\n await Model.findByIdAndUpdate(testDoc._id, { deletedAt: new Date() });\n await Model.findByIdAndUpdate(testDoc._id, { deletedAt: null });\n const restored = await Model.findById(testDoc._id);\n expect(restored!.deletedAt).toBeNull();\n });\n });\n }\n\n // Slug preset tests\n if (presets.includes('slugLookup')) {\n describe('Slug Lookup', () => {\n it('should have slug field', async () => {\n const doc = await Model.create(fixtures.valid);\n this._createdIds.push(doc._id);\n expect(doc.slug).toBeDefined();\n });\n\n it('should generate slug from name', async () => {\n const doc = await Model.create({ ...fixtures.valid, name: 'Test Slug Name' });\n this._createdIds.push(doc._id);\n expect(doc.slug).toMatch(/test-slug-name/i);\n });\n });\n }\n\n // Tree preset tests\n if (presets.includes('tree')) {\n describe('Tree Structure', () => {\n it('should allow parent reference', async () => {\n const parent = await Model.create(fixtures.valid);\n this._createdIds.push(parent._id);\n\n const child = await Model.create({\n ...fixtures.valid,\n parent: parent._id,\n });\n this._createdIds.push(child._id);\n\n expect(child.parent.toString()).toEqual(parent._id.toString());\n });\n\n it('should support displayOrder', async () => {\n const doc = await Model.create({\n ...fixtures.valid,\n displayOrder: 5,\n });\n this._createdIds.push(doc._id);\n expect(doc.displayOrder).toEqual(5);\n });\n });\n }\n\n // Multi-tenant preset tests\n if (presets.includes('multiTenant')) {\n describe('Multi-Tenant', () => {\n it('should require organizationId', async () => {\n const docWithoutOrg = { ...fixtures.valid };\n delete (docWithoutOrg as any).organizationId;\n await expect(Model.create(docWithoutOrg)).rejects.toThrow();\n });\n });\n }\n\n // Owned by user preset tests\n if (presets.includes('ownedByUser')) {\n describe('Owned By User', () => {\n it('should require userId', async () => {\n const docWithoutUser = { ...fixtures.valid };\n delete (docWithoutUser as any).userId;\n await expect(Model.create(docWithoutUser)).rejects.toThrow();\n });\n });\n }\n });\n }\n\n /**\n * Run field-level permission tests\n *\n * Auto-generates tests for each field permission:\n * - hidden: field is stripped from responses\n * - visibleTo: field only shown to specified roles\n * - writableBy: field stripped from writes by non-privileged users\n * - redactFor: field shows redacted value for specified roles\n */\n runFieldPermissions(): void {\n const { resource } = this;\n const fieldPerms = resource.fields;\n\n if (!fieldPerms || Object.keys(fieldPerms).length === 0) return;\n\n describe(`${resource.displayName} Field Permissions`, () => {\n for (const [field, perm] of Object.entries(fieldPerms)) {\n switch (perm._type) {\n case 'hidden':\n it(`should always hide field '${field}'`, () => {\n const data = { [field]: 'secret', otherField: 'visible' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, []);\n expect(result[field]).toBeUndefined();\n expect(result.otherField).toBe('visible');\n });\n\n it(`should strip hidden field '${field}' from writes`, () => {\n const body = { [field]: 'attempt', name: 'test' } as Record<string, unknown>;\n const result = applyFieldWritePermissions(body, fieldPerms, []);\n expect(result[field]).toBeUndefined();\n expect(result.name).toBe('test');\n });\n break;\n\n case 'visibleTo':\n it(`should hide field '${field}' from non-privileged users`, () => {\n const data = { [field]: 'sensitive' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, ['viewer']);\n expect(result[field]).toBeUndefined();\n });\n\n if (perm.roles && perm.roles.length > 0) {\n const allowedRole = perm.roles[0]!;\n it(`should show field '${field}' to roles: ${[...perm.roles].join(', ')}`, () => {\n const data = { [field]: 'sensitive' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, [allowedRole]);\n expect(result[field]).toBe('sensitive');\n });\n }\n break;\n\n case 'writableBy':\n it(`should strip field '${field}' from writes by non-privileged users`, () => {\n const body = { [field]: 'new-value', name: 'test' } as Record<string, unknown>;\n const result = applyFieldWritePermissions(body, fieldPerms, ['viewer']);\n expect(result[field]).toBeUndefined();\n expect(result.name).toBe('test');\n });\n\n if (perm.roles && perm.roles.length > 0) {\n const writeRole = perm.roles[0]!;\n it(`should allow writing field '${field}' by roles: ${[...perm.roles].join(', ')}`, () => {\n const body = { [field]: 'new-value' } as Record<string, unknown>;\n const result = applyFieldWritePermissions(body, fieldPerms, [writeRole]);\n expect(result[field]).toBe('new-value');\n });\n }\n break;\n\n case 'redactFor':\n if (perm.roles && perm.roles.length > 0) {\n const redactRole = perm.roles[0]!;\n it(`should redact field '${field}' for roles: ${[...perm.roles].join(', ')}`, () => {\n const data = { [field]: 'real-value' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, [redactRole]);\n expect(result[field]).toBe(perm.redactValue ?? '***');\n });\n }\n\n it(`should show real value of field '${field}' to non-redacted roles`, () => {\n const data = { [field]: 'real-value' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, ['unrelated-role']);\n expect(result[field]).toBe('real-value');\n });\n break;\n }\n }\n });\n }\n\n /**\n * Run pipeline configuration tests\n *\n * Validates that pipeline steps are properly configured:\n * - All steps have names\n * - All steps have valid _type discriminants\n * - Operation filters (if set) use valid CRUD operation names\n */\n runPipeline(): void {\n const { resource } = this;\n const pipe = resource.pipe;\n\n if (!pipe) return;\n\n const validOps: Set<string> = new Set(CRUD_OPERATIONS);\n\n describe(`${resource.displayName} Pipeline`, () => {\n const steps = collectPipelineSteps(pipe);\n\n it('should have at least one pipeline step', () => {\n expect(steps.length).toBeGreaterThan(0);\n });\n\n for (const step of steps) {\n it(`${step._type} '${step.name}' should have a valid type`, () => {\n expect(['guard', 'transform', 'interceptor']).toContain(step._type);\n });\n\n it(`${step._type} '${step.name}' should have a name`, () => {\n expect(step.name).toBeTruthy();\n expect(typeof step.name).toBe('string');\n });\n\n it(`${step._type} '${step.name}' should have a handler function`, () => {\n expect(typeof step.handler).toBe('function');\n });\n\n if (step.operations?.length) {\n it(`${step._type} '${step.name}' should target valid operations`, () => {\n for (const op of step.operations!) {\n expect(validOps.has(op)).toBe(true);\n }\n });\n }\n }\n });\n }\n\n /**\n * Run event definition tests\n *\n * Validates that events are properly defined:\n * - All events have handler functions\n * - Event names follow resource:action convention\n * - Schema definitions (if present) are valid objects\n */\n runEvents(): void {\n const { resource } = this;\n const events = resource.events;\n\n if (!events || Object.keys(events).length === 0) return;\n\n describe(`${resource.displayName} Events`, () => {\n for (const [action, def] of Object.entries(events)) {\n it(`event '${resource.name}:${action}' should have a handler function`, () => {\n expect(typeof def.handler).toBe('function');\n });\n\n it(`event '${resource.name}:${action}' should have a name`, () => {\n expect(def.name).toBeTruthy();\n expect(typeof def.name).toBe('string');\n });\n\n if (def.schema) {\n it(`event '${resource.name}:${action}' schema should be an object`, () => {\n expect(typeof def.schema).toBe('object');\n expect(def.schema).not.toBeNull();\n });\n }\n }\n });\n }\n}\n\n/**\n * Collect all pipeline steps from a PipelineConfig (flat array or per-operation map)\n */\nfunction collectPipelineSteps(pipe: PipelineConfig): PipelineStep[] {\n if (Array.isArray(pipe)) return pipe;\n\n const seen = new Set<string>();\n const steps: PipelineStep[] = [];\n\n for (const opSteps of Object.values(pipe)) {\n if (Array.isArray(opSteps)) {\n for (const step of opSteps) {\n const key = `${step._type}:${step.name}`;\n if (!seen.has(key)) {\n seen.add(key);\n steps.push(step);\n }\n }\n }\n }\n\n return steps;\n}\n\n/**\n * Create a test harness for an Arc resource\n *\n * @param resource - The Arc resource definition to test\n * @param options - Test harness configuration\n * @returns Test harness instance\n *\n * @example\n * import { createTestHarness } from '@classytic/arc/testing';\n *\n * const harness = createTestHarness(productResource, {\n * fixtures: {\n * valid: { name: 'Product', price: 100 },\n * update: { name: 'Updated' },\n * },\n * });\n *\n * harness.runAll(); // Generates 50+ baseline tests\n */\nexport function createTestHarness<T = any>(\n resource: ResourceDefinition,\n options: TestHarnessOptions<T>\n): TestHarness<T> {\n return new TestHarness<T>(resource, options);\n}\n\n/**\n * Test file generation options\n */\nexport interface GenerateTestFileOptions {\n /** Applied presets (e.g., ['softDelete', 'slugLookup']) */\n presets?: string[];\n /** Module path for imports (default: '.') */\n modulePath?: string;\n}\n\n/**\n * Generate test file content for a resource\n *\n * Useful for scaffolding new resource tests via CLI\n *\n * @param resourceName - Resource name in kebab-case (e.g., 'product')\n * @param options - Generation options\n * @returns Complete test file content as string\n *\n * @example\n * const testContent = generateTestFile('product', {\n * presets: ['softDelete'],\n * modulePath: './modules/catalog',\n * });\n * fs.writeFileSync('product.test.js', testContent);\n */\nexport function generateTestFile(\n resourceName: string,\n options: GenerateTestFileOptions = {}\n): string {\n const { presets = [], modulePath = '.' } = options;\n const className = resourceName\n .split('-')\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join('');\n const varName = className.charAt(0).toLowerCase() + className.slice(1);\n\n return `/**\n * ${className} Resource Tests\n *\n * Auto-generated baseline tests. Customize as needed.\n */\n\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\nimport mongoose from 'mongoose';\nimport { createTestHarness } from '@classytic/arc/testing';\nimport ${varName}Resource from '${modulePath}/${resourceName}.resource.js';\nimport ${className} from '${modulePath}/${resourceName}.model.js';\n\nconst MONGO_URI = process.env.MONGO_TEST_URI || 'mongodb://localhost:27017/${resourceName}-test';\n\n// Test fixtures\nconst fixtures = {\n valid: {\n name: 'Test ${className}',\n // Add required fields here\n },\n update: {\n name: 'Updated ${className}',\n },\n invalid: {\n // Empty or invalid data\n },\n};\n\n// Create test harness\nconst harness = createTestHarness(${varName}Resource, {\n fixtures,\n mongoUri: MONGO_URI,\n});\n\n// Run all baseline tests\nharness.runAll();\n\n// Custom tests\ndescribe('${className} Custom Tests', () => {\n let testId;\n\n beforeAll(async () => {\n await mongoose.connect(MONGO_URI);\n });\n\n afterAll(async () => {\n if (testId) {\n await ${className}.findByIdAndDelete(testId);\n }\n await mongoose.disconnect();\n });\n\n // Add your custom tests here\n it('should pass custom validation', async () => {\n // Example: const doc = await ${className}.create(fixtures.valid);\n // testId = doc._id;\n // expect(doc.someField).toBe('expectedValue');\n expect(true).toBe(true);\n });\n});\n`;\n}\n\n/**\n * Run config-level tests for a resource (no DB required)\n *\n * Tests field permissions, pipeline configuration, and event definitions.\n * Works with any adapter — no Mongoose dependency.\n *\n * @param resource - The Arc resource definition to test\n *\n * @example\n * ```typescript\n * import { createConfigTestSuite } from '@classytic/arc/testing';\n * import productResource from './product.resource.js';\n *\n * // Generates field permission, pipeline, and event tests\n * createConfigTestSuite(productResource);\n * ```\n */\nexport function createConfigTestSuite(resource: ResourceDefinition<unknown>): void {\n const fieldPerms = resource.fields;\n const pipe = resource.pipe;\n const events = resource.events;\n\n // Field permissions\n if (fieldPerms && Object.keys(fieldPerms).length > 0) {\n runFieldPermissionTests(resource.displayName, fieldPerms);\n }\n\n // Pipeline\n if (pipe) {\n runPipelineTests(resource.displayName, pipe);\n }\n\n // Events\n if (events && Object.keys(events).length > 0) {\n runEventTests(resource.name, resource.displayName, events);\n }\n\n // Permissions configuration\n if (resource.permissions && Object.keys(resource.permissions).length > 0) {\n describe(`${resource.displayName} Permission Config`, () => {\n for (const op of CRUD_OPERATIONS) {\n const check = resource.permissions[op];\n if (check) {\n it(`${op} permission should be a function`, () => {\n expect(typeof check).toBe('function');\n });\n }\n }\n });\n }\n}\n\nfunction runFieldPermissionTests(displayName: string, fieldPerms: FieldPermissionMap): void {\n describe(`${displayName} Field Permissions`, () => {\n for (const [field, perm] of Object.entries(fieldPerms)) {\n switch (perm._type) {\n case 'hidden':\n it(`should always hide field '${field}'`, () => {\n const data = { [field]: 'secret', other: 'visible' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, []);\n expect(result[field]).toBeUndefined();\n });\n\n it(`should strip hidden field '${field}' from writes`, () => {\n const body = { [field]: 'attempt', name: 'test' } as Record<string, unknown>;\n const result = applyFieldWritePermissions(body, fieldPerms, []);\n expect(result[field]).toBeUndefined();\n });\n break;\n\n case 'visibleTo':\n it(`should hide field '${field}' from non-privileged users`, () => {\n const data = { [field]: 'sensitive' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, ['_no_role_']);\n expect(result[field]).toBeUndefined();\n });\n\n if (perm.roles && perm.roles.length > 0) {\n const allowedRole = perm.roles[0]!;\n it(`should show field '${field}' to roles: ${[...perm.roles].join(', ')}`, () => {\n const data = { [field]: 'sensitive' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, [allowedRole]);\n expect(result[field]).toBe('sensitive');\n });\n }\n break;\n\n case 'writableBy':\n it(`should strip field '${field}' from writes by non-privileged users`, () => {\n const body = { [field]: 'v', name: 'test' } as Record<string, unknown>;\n const result = applyFieldWritePermissions(body, fieldPerms, ['_no_role_']);\n expect(result[field]).toBeUndefined();\n });\n\n if (perm.roles && perm.roles.length > 0) {\n const writeRole = perm.roles[0]!;\n it(`should allow writing field '${field}' by roles: ${[...perm.roles].join(', ')}`, () => {\n const body = { [field]: 'v' } as Record<string, unknown>;\n const result = applyFieldWritePermissions(body, fieldPerms, [writeRole]);\n expect(result[field]).toBe('v');\n });\n }\n break;\n\n case 'redactFor':\n if (perm.roles && perm.roles.length > 0) {\n const redactRole = perm.roles[0]!;\n it(`should redact field '${field}' for roles: ${[...perm.roles].join(', ')}`, () => {\n const data = { [field]: 'real' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, [redactRole]);\n expect(result[field]).toBe(perm.redactValue ?? '***');\n });\n }\n\n it(`should show real value of field '${field}' to non-redacted roles`, () => {\n const data = { [field]: 'real' } as Record<string, unknown>;\n const result = applyFieldReadPermissions(data, fieldPerms, ['_other_']);\n expect(result[field]).toBe('real');\n });\n break;\n }\n }\n });\n}\n\nfunction runPipelineTests(displayName: string, pipe: PipelineConfig): void {\n const steps = collectPipelineSteps(pipe);\n if (steps.length === 0) return;\n\n const validOps: Set<string> = new Set(CRUD_OPERATIONS);\n\n describe(`${displayName} Pipeline`, () => {\n it('should have at least one pipeline step', () => {\n expect(steps.length).toBeGreaterThan(0);\n });\n\n for (const step of steps) {\n it(`${step._type} '${step.name}' should have a valid type`, () => {\n expect(['guard', 'transform', 'interceptor']).toContain(step._type);\n });\n\n it(`${step._type} '${step.name}' should have a handler function`, () => {\n expect(typeof step.handler).toBe('function');\n });\n\n if (step.operations?.length) {\n it(`${step._type} '${step.name}' should target valid operations`, () => {\n for (const op of step.operations!) {\n expect(validOps.has(op)).toBe(true);\n }\n });\n }\n }\n });\n}\n\nfunction runEventTests(\n resourceName: string,\n displayName: string,\n events: Record<string, import('../types/index.js').EventDefinition>,\n): void {\n describe(`${displayName} Events`, () => {\n for (const [action, def] of Object.entries(events)) {\n it(`event '${resourceName}:${action}' should have a handler function`, () => {\n expect(typeof def.handler).toBe('function');\n });\n\n it(`event '${resourceName}:${action}' should have a name`, () => {\n expect(def.name).toBeTruthy();\n });\n\n if (def.schema) {\n it(`event '${resourceName}:${action}' schema should be an object`, () => {\n expect(typeof def.schema).toBe('object');\n expect(def.schema).not.toBeNull();\n });\n }\n }\n });\n}\n","/**\n * Testing Utilities - Database Helpers\n *\n * Utilities for managing test databases and fixtures\n */\n\nimport { beforeAll, afterAll, afterEach } from 'vitest';\nimport mongoose from 'mongoose';\nimport type { Connection } from 'mongoose';\n\n/**\n * Test database manager\n */\nexport class TestDatabase {\n private connection?: Connection;\n private dbName: string;\n\n constructor(dbName: string = `test_${Date.now()}`) {\n this.dbName = dbName;\n }\n\n /**\n * Connect to test database\n */\n async connect(uri?: string): Promise<Connection> {\n const mongoUri = uri || process.env.MONGO_TEST_URI || 'mongodb://localhost:27017';\n const fullUri = `${mongoUri}/${this.dbName}`;\n\n this.connection = await mongoose.createConnection(fullUri).asPromise();\n return this.connection;\n }\n\n /**\n * Disconnect and cleanup\n */\n async disconnect(): Promise<void> {\n if (this.connection) {\n await this.connection.dropDatabase();\n await this.connection.close();\n this.connection = undefined;\n }\n }\n\n /**\n * Clear all collections\n */\n async clear(): Promise<void> {\n if (!this.connection?.db) {\n throw new Error('Database not connected');\n }\n\n const collections = await this.connection.db.collections();\n await Promise.all(collections.map((collection) => collection.deleteMany({})));\n }\n\n /**\n * Get connection\n */\n getConnection(): Connection {\n if (!this.connection) {\n throw new Error('Database not connected');\n }\n return this.connection;\n }\n}\n\n/**\n * Higher-order function to wrap tests with database setup/teardown\n *\n * @example\n * describe('Product Tests', () => {\n * withTestDb(async (db) => {\n * test('create product', async () => {\n * const Product = db.getConnection().model('Product', schema);\n * const product = await Product.create({ name: 'Test' });\n * expect(product.name).toBe('Test');\n * });\n * });\n * });\n */\nexport function withTestDb(\n tests: (db: TestDatabase) => void | Promise<void>,\n options: { uri?: string; dbName?: string } = {}\n): void {\n const db = new TestDatabase(options.dbName);\n\n beforeAll(async () => {\n await db.connect(options.uri);\n });\n\n afterAll(async () => {\n await db.disconnect();\n });\n\n afterEach(async () => {\n await db.clear();\n });\n\n tests(db);\n}\n\n/**\n * Create test fixtures\n *\n * @example\n * const fixtures = new TestFixtures(connection);\n *\n * await fixtures.load('products', [\n * { name: 'Product 1', price: 100 },\n * { name: 'Product 2', price: 200 },\n * ]);\n *\n * const products = await fixtures.get('products');\n */\nexport class TestFixtures {\n private fixtures: Map<string, any[]> = new Map();\n private connection: Connection;\n\n constructor(connection: Connection) {\n this.connection = connection;\n }\n\n /**\n * Load fixtures into a collection\n */\n async load<T = any>(collectionName: string, data: Partial<T>[]): Promise<T[]> {\n const collection = this.connection.collection(collectionName);\n const result = await collection.insertMany(data as any[]);\n\n const insertedDocs = Object.values(result.insertedIds).map((id, index) => ({\n ...data[index],\n _id: id,\n })) as T[];\n\n this.fixtures.set(collectionName, insertedDocs);\n return insertedDocs;\n }\n\n /**\n * Get loaded fixtures\n */\n get<T = any>(collectionName: string): T[] {\n return (this.fixtures.get(collectionName) || []) as T[];\n }\n\n /**\n * Get first fixture\n */\n getFirst<T = any>(collectionName: string): T | null {\n const items = this.get<T>(collectionName);\n return items[0] || null;\n }\n\n /**\n * Clear all fixtures\n */\n async clear(): Promise<void> {\n for (const collectionName of this.fixtures.keys()) {\n const collection = this.connection.collection(collectionName);\n const ids = this.fixtures.get(collectionName)?.map((item) => item._id) || [];\n await collection.deleteMany({ _id: { $in: ids } });\n }\n this.fixtures.clear();\n }\n}\n\n/**\n * In-memory MongoDB for ultra-fast tests\n *\n * Requires: mongodb-memory-server\n *\n * @example\n * import { InMemoryDatabase } from '@classytic/arc/testing';\n *\n * describe('Fast Tests', () => {\n * const memoryDb = new InMemoryDatabase();\n *\n * beforeAll(async () => {\n * await memoryDb.start();\n * });\n *\n * afterAll(async () => {\n * await memoryDb.stop();\n * });\n *\n * test('create user', async () => {\n * const uri = memoryDb.getUri();\n * // Use uri for connection\n * });\n * });\n */\nexport class InMemoryDatabase {\n private mongod?: any;\n private uri?: string;\n\n /**\n * Start in-memory MongoDB\n */\n async start(): Promise<string> {\n try {\n const { MongoMemoryServer } = await import('mongodb-memory-server');\n this.mongod = await MongoMemoryServer.create();\n const uri = this.mongod.getUri() as string;\n this.uri = uri;\n return uri;\n } catch {\n throw new Error(\n 'mongodb-memory-server not installed. Install with: npm install -D mongodb-memory-server'\n );\n }\n }\n\n /**\n * Stop in-memory MongoDB\n */\n async stop(): Promise<void> {\n if (this.mongod) {\n await this.mongod.stop();\n this.mongod = undefined;\n this.uri = undefined;\n }\n }\n\n /**\n * Get connection URI\n */\n getUri(): string {\n if (!this.uri) {\n throw new Error('In-memory database not started');\n }\n return this.uri;\n }\n}\n\n/**\n * Database transaction helper for testing\n */\nexport class TestTransaction {\n private session?: any;\n private connection: Connection;\n\n constructor(connection: Connection) {\n this.connection = connection;\n }\n\n /**\n * Start transaction\n */\n async start(): Promise<void> {\n this.session = await this.connection.startSession();\n this.session.startTransaction();\n }\n\n /**\n * Commit transaction\n */\n async commit(): Promise<void> {\n if (!this.session) {\n throw new Error('Transaction not started');\n }\n await this.session.commitTransaction();\n await this.session.endSession();\n this.session = undefined;\n }\n\n /**\n * Rollback transaction\n */\n async rollback(): Promise<void> {\n if (!this.session) {\n throw new Error('Transaction not started');\n }\n await this.session.abortTransaction();\n await this.session.endSession();\n this.session = undefined;\n }\n\n /**\n * Get session\n */\n getSession(): any {\n if (!this.session) {\n throw new Error('Transaction not started');\n }\n return this.session;\n }\n}\n\n/**\n * Seed data helper\n */\nexport class TestSeeder {\n private connection: Connection;\n\n constructor(connection: Connection) {\n this.connection = connection;\n }\n\n /**\n * Seed collection with data\n */\n async seed<T>(collectionName: string, generator: () => T[], count: number = 10): Promise<T[]> {\n const data = Array.from({ length: count }, () => generator()).flat();\n const collection = this.connection.collection(collectionName);\n const result = await collection.insertMany(data as any[]);\n\n return Object.values(result.insertedIds).map((id, index) => ({\n ...data[index],\n _id: id,\n })) as T[];\n }\n\n /**\n * Clear collection\n */\n async clear(collectionName: string): Promise<void> {\n const collection = this.connection.collection(collectionName);\n await collection.deleteMany({});\n }\n\n /**\n * Clear all collections\n */\n async clearAll(): Promise<void> {\n if (!this.connection.db) {\n throw new Error('Database not connected');\n }\n const collections = await this.connection.db.collections();\n await Promise.all(collections.map((collection) => collection.deleteMany({})));\n }\n}\n\n/**\n * Database snapshot helper for rollback testing\n */\nexport class DatabaseSnapshot {\n private snapshots: Map<string, any[]> = new Map();\n private connection: Connection;\n\n constructor(connection: Connection) {\n this.connection = connection;\n }\n\n /**\n * Take snapshot of current database state\n */\n async take(): Promise<void> {\n if (!this.connection.db) {\n throw new Error('Database not connected');\n }\n const collections = await this.connection.db.collections();\n\n for (const collection of collections) {\n const data = await collection.find({}).toArray();\n this.snapshots.set(collection.collectionName, data);\n }\n }\n\n /**\n * Restore database to snapshot\n */\n async restore(): Promise<void> {\n if (!this.connection.db) {\n throw new Error('Database not connected');\n }\n // Clear current data\n const collections = await this.connection.db.collections();\n await Promise.all(collections.map((collection) => collection.deleteMany({})));\n\n // Restore snapshot\n for (const [collectionName, data] of this.snapshots.entries()) {\n if (data.length > 0) {\n const collection = this.connection.collection(collectionName);\n await collection.insertMany(data);\n }\n }\n }\n\n /**\n * Clear snapshot\n */\n clear(): void {\n this.snapshots.clear();\n }\n}\n","/**\n * Testing Utilities - Test App Factory\n *\n * Create Fastify test instances with Arc configuration\n */\n\nimport Fastify, { type FastifyInstance } from 'fastify';\nimport type { CreateAppOptions } from '../factory/types.js';\nimport { InMemoryDatabase } from './dbHelpers.js';\n\nexport interface CreateTestAppOptions extends Partial<CreateAppOptions> {\n /**\n * Use in-memory MongoDB for faster tests (default: true)\n * Requires: mongodb-memory-server\n *\n * Set to false to use a provided mongoUri instead\n */\n useInMemoryDb?: boolean;\n\n /**\n * MongoDB connection URI (only used if useInMemoryDb is false)\n */\n mongoUri?: string;\n}\n\nexport interface TestAppResult {\n /** Fastify app instance */\n app: FastifyInstance;\n\n /**\n * Cleanup function to close app and disconnect database\n * Call this in afterAll() or afterEach()\n */\n close: () => Promise<void>;\n\n /** MongoDB connection URI (useful for connecting models) */\n mongoUri?: string;\n}\n\n/**\n * Create a test application instance with optional in-memory MongoDB\n *\n * **Performance Boost**: Uses in-memory MongoDB by default for 10x faster tests.\n *\n * @example Basic usage with in-memory DB\n * ```typescript\n * import { createTestApp } from '@classytic/arc/testing';\n *\n * describe('API Tests', () => {\n * let testApp: TestAppResult;\n *\n * beforeAll(async () => {\n * testApp = await createTestApp({\n * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },\n * });\n * });\n *\n * afterAll(async () => {\n * await testApp.close(); // Cleans up DB and disconnects\n * });\n *\n * test('GET /health', async () => {\n * const response = await testApp.app.inject({\n * method: 'GET',\n * url: '/health',\n * });\n * expect(response.statusCode).toBe(200);\n * });\n * });\n * ```\n *\n * @example Using external MongoDB\n * ```typescript\n * const testApp = await createTestApp({\n * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },\n * useInMemoryDb: false,\n * mongoUri: 'mongodb://localhost:27017/test-db',\n * });\n * ```\n *\n * @example Accessing MongoDB URI for model connections\n * ```typescript\n * const testApp = await createTestApp({\n * auth: { type: 'jwt', jwt: { secret: 'test-secret' } },\n * });\n * await mongoose.connect(testApp.mongoUri); // Connect your models\n * ```\n */\nexport async function createTestApp(\n options: CreateTestAppOptions = {}\n): Promise<TestAppResult> {\n const { createApp } = await import('../factory/createApp.js');\n const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;\n\n // Default auth config for tests\n const defaultAuth = { type: 'jwt' as const, jwt: { secret: 'test-secret-32-chars-minimum-len' } };\n\n let inMemoryDb: InMemoryDatabase | null = null;\n let mongoUri: string | undefined = providedMongoUri;\n\n // Start in-memory MongoDB if enabled and no URI provided\n if (useInMemoryDb && !providedMongoUri) {\n try {\n inMemoryDb = new InMemoryDatabase();\n mongoUri = await inMemoryDb.start();\n } catch (err) {\n console.warn(\n '[createTestApp] Failed to start in-memory MongoDB:',\n (err as Error).message,\n '\\nFalling back to external MongoDB or no DB connection.'\n );\n }\n }\n\n const testDefaults: Partial<CreateAppOptions> = {\n preset: 'testing',\n logger: false, // Disable logging in tests\n helmet: false,\n cors: false,\n rateLimit: false,\n underPressure: false,\n auth: defaultAuth,\n };\n\n const app = await createApp({\n ...testDefaults,\n ...appOptions, // User options override defaults (including auth)\n });\n\n // Return app with cleanup function\n return {\n app,\n mongoUri,\n async close() {\n await app.close();\n if (inMemoryDb) {\n await inMemoryDb.stop();\n }\n },\n };\n}\n\n/**\n * Create a minimal Fastify instance for unit tests\n *\n * Use when you don't need Arc's full plugin stack\n *\n * @example\n * const app = createMinimalTestApp();\n * app.get('/test', async () => ({ success: true }));\n *\n * const response = await app.inject({ method: 'GET', url: '/test' });\n * expect(response.json()).toEqual({ success: true });\n */\nexport function createMinimalTestApp(options: Partial<any> = {}): FastifyInstance {\n return Fastify({\n logger: false,\n ...options,\n });\n}\n\n/**\n * Test request builder for cleaner tests\n *\n * @example\n * const request = new TestRequestBuilder(app)\n * .get('/products')\n * .withAuth(mockUser)\n * .withQuery({ page: 1, limit: 10 });\n *\n * const response = await request.send();\n * expect(response.statusCode).toBe(200);\n */\nexport class TestRequestBuilder {\n private method: string = 'GET';\n private url: string = '/';\n private body?: any;\n private query?: Record<string, any>;\n private headers: Record<string, string> = {};\n private app: FastifyInstance;\n\n constructor(app: FastifyInstance) {\n this.app = app;\n }\n\n get(url: string) {\n this.method = 'GET';\n this.url = url;\n return this;\n }\n\n post(url: string) {\n this.method = 'POST';\n this.url = url;\n return this;\n }\n\n put(url: string) {\n this.method = 'PUT';\n this.url = url;\n return this;\n }\n\n patch(url: string) {\n this.method = 'PATCH';\n this.url = url;\n return this;\n }\n\n delete(url: string) {\n this.method = 'DELETE';\n this.url = url;\n return this;\n }\n\n withBody(body: any) {\n this.body = body;\n return this;\n }\n\n withQuery(query: Record<string, any>) {\n this.query = query;\n return this;\n }\n\n withHeader(key: string, value: string) {\n this.headers[key] = value;\n return this;\n }\n\n withAuth(userOrHeaders: Record<string, unknown>) {\n if ('authorization' in userOrHeaders || 'Authorization' in userOrHeaders) {\n // Pre-built headers (Better Auth tokens, external auth)\n for (const [key, value] of Object.entries(userOrHeaders)) {\n if (typeof value === 'string') {\n this.headers[key] = value;\n }\n }\n } else {\n // JWT payload — sign with app's JWT plugin\n const token = this.app.jwt?.sign?.(userOrHeaders) || 'mock-token';\n this.headers['Authorization'] = `Bearer ${token}`;\n }\n return this;\n }\n\n withContentType(type: string) {\n this.headers['Content-Type'] = type;\n return this;\n }\n\n async send() {\n return this.app.inject({\n method: this.method as any,\n url: this.url,\n payload: this.body,\n query: this.query,\n headers: this.headers,\n });\n }\n}\n\n/**\n * Helper to create a test request builder\n */\nexport function request(app: FastifyInstance) {\n return new TestRequestBuilder(app);\n}\n\n/**\n * Test helper for authentication\n */\nexport function createTestAuth(app: FastifyInstance) {\n return {\n /**\n * Generate a JWT token for testing\n */\n generateToken(user: any): string {\n if (!app.jwt) {\n throw new Error('JWT plugin not registered');\n }\n return app.jwt.sign(user);\n },\n\n /**\n * Decode a JWT token\n */\n decodeToken(token: string): any {\n if (!app.jwt) {\n throw new Error('JWT plugin not registered');\n }\n return app.jwt.decode(token);\n },\n\n /**\n * Verify a JWT token\n */\n async verifyToken(token: string): Promise<any> {\n if (!app.jwt) {\n throw new Error('JWT plugin not registered');\n }\n return app.jwt.verify(token);\n },\n };\n}\n\n/**\n * Snapshot testing helper for API responses\n */\nexport function createSnapshotMatcher() {\n return {\n /**\n * Match response structure (ignores dynamic values like timestamps)\n */\n matchStructure(response: any, expected: any): boolean {\n if (typeof response !== typeof expected) {\n return false;\n }\n\n if (Array.isArray(response) && Array.isArray(expected)) {\n return response.length === expected.length;\n }\n\n if (typeof response === 'object' && response !== null) {\n const responseKeys = Object.keys(response).sort();\n const expectedKeys = Object.keys(expected).sort();\n\n if (JSON.stringify(responseKeys) !== JSON.stringify(expectedKeys)) {\n return false;\n }\n\n for (const key of responseKeys) {\n if (!this.matchStructure(response[key], expected[key])) {\n return false;\n }\n }\n\n return true;\n }\n\n return true; // Primitives - don't compare values\n },\n };\n}\n\n/**\n * Bulk test data loader\n */\nexport class TestDataLoader {\n private data: Map<string, any[]> = new Map();\n private app: FastifyInstance;\n\n constructor(app: FastifyInstance) {\n this.app = app;\n }\n\n /**\n * Load test data into database\n */\n async load(collection: string, items: any[]) {\n // Store for cleanup\n this.data.set(collection, items);\n\n // Load into database (assumes mongoose/mongodb)\n // This is a placeholder - implement based on your DB setup\n return items;\n }\n\n /**\n * Clear all loaded test data\n */\n async cleanup() {\n for (const [collection, items] of this.data.entries()) {\n // Cleanup logic here\n // e.g., await Model.deleteMany({ _id: { $in: items.map(i => i._id) } })\n }\n this.data.clear();\n }\n}\n","/**\n * Testing Utilities - Mock Factories\n *\n * Create mock repositories, controllers, and services for testing.\n * Uses Vitest for mocking (compatible with Jest API).\n */\n\nimport { vi, type Mock } from 'vitest';\nimport type { CrudRepository, AnyRecord, PaginatedResult } from '../types/index.js';\n\n/**\n * Extended repository interface for testing (includes optional preset methods)\n */\nexport interface MockRepository<T> extends CrudRepository<T> {\n // Optional preset methods for testing\n getBySlug?: Mock;\n getDeleted?: Mock;\n restore?: Mock;\n getTree?: Mock;\n getChildren?: Mock;\n [key: string]: unknown;\n}\n\n/**\n * Create a mock repository for testing\n *\n * @example\n * const mockRepo = createMockRepository<Product>({\n * getById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),\n * create: vi.fn().mockImplementation(data => Promise.resolve({ id: '1', ...data })),\n * });\n *\n * await mockRepo.getById('1'); // Returns mocked product\n */\nexport function createMockRepository<T extends AnyRecord = AnyRecord>(\n overrides: Partial<MockRepository<T>> = {}\n): MockRepository<T> {\n const defaultMock: MockRepository<T> = {\n // MongoKit-compatible CRUD methods\n getAll: vi.fn().mockResolvedValue({\n docs: [],\n total: 0,\n page: 1,\n limit: 20,\n pages: 0,\n hasNext: false,\n hasPrev: false,\n } as PaginatedResult<T>),\n\n getById: vi.fn().mockResolvedValue(null),\n\n create: vi.fn().mockImplementation((data: Partial<T>) =>\n Promise.resolve({ _id: 'mock-id', ...data } as unknown as T)\n ),\n\n update: vi.fn().mockImplementation((_id: string, data: Partial<T>) =>\n Promise.resolve({ _id: 'mock-id', ...data } as unknown as T)\n ),\n\n delete: vi.fn().mockResolvedValue({ success: true, message: 'Deleted' }),\n\n // Optional preset methods\n getBySlug: vi.fn().mockResolvedValue(null),\n getDeleted: vi.fn().mockResolvedValue([]),\n restore: vi.fn().mockResolvedValue(null),\n getTree: vi.fn().mockResolvedValue([]),\n getChildren: vi.fn().mockResolvedValue([]),\n\n // Apply overrides\n ...overrides,\n };\n\n return defaultMock;\n}\n\n/**\n * Create a mock user for authentication testing\n */\nexport function createMockUser(overrides: Partial<AnyRecord> = {}) {\n return {\n _id: 'mock-user-id',\n id: 'mock-user-id',\n email: 'test@example.com',\n roles: ['user'],\n organizationId: null,\n ...overrides,\n };\n}\n\n/**\n * Create a mock Fastify request\n */\nexport function createMockRequest(overrides: Partial<AnyRecord> = {}) {\n return {\n body: {},\n params: {},\n query: {},\n headers: {},\n user: createMockUser(),\n context: {},\n log: {\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n debug: vi.fn(),\n },\n ...overrides,\n } as unknown;\n}\n\n/**\n * Create a mock Fastify reply\n */\nexport function createMockReply() {\n const reply = {\n code: vi.fn().mockReturnThis(),\n send: vi.fn().mockReturnThis(),\n header: vi.fn().mockReturnThis(),\n headers: vi.fn().mockReturnThis(),\n status: vi.fn().mockReturnThis(),\n type: vi.fn().mockReturnThis(),\n redirect: vi.fn().mockReturnThis(),\n callNotFound: vi.fn().mockReturnThis(),\n sent: false,\n };\n\n return reply as unknown;\n}\n\n/**\n * Create a mock controller for testing\n */\nexport function createMockController(repository: CrudRepository<AnyRecord>) {\n return {\n repository,\n list: vi.fn(),\n get: vi.fn(),\n create: vi.fn(),\n update: vi.fn(),\n delete: vi.fn(),\n };\n}\n\n/**\n * Create mock data factory\n *\n * @example\n * const productFactory = createDataFactory<Product>({\n * name: () => faker.commerce.productName(),\n * price: () => faker.number.int({ min: 10, max: 1000 }),\n * sku: (i) => `SKU-${i}`,\n * });\n *\n * const product = productFactory.build();\n * const products = productFactory.buildMany(10);\n */\nexport function createDataFactory<T extends AnyRecord>(\n template: Record<keyof T, (index: number) => unknown>\n) {\n let counter = 0;\n\n return {\n build(overrides: Partial<T> = {}): T {\n const index = counter++;\n const data = {} as T;\n\n for (const [key, generator] of Object.entries(template)) {\n (data as AnyRecord)[key] = generator(index);\n }\n\n return { ...data, ...overrides };\n },\n\n buildMany(count: number, overrides: Partial<T> = {}): T[] {\n return Array.from({ length: count }, () => this.build(overrides));\n },\n\n reset() {\n counter = 0;\n },\n };\n}\n\n/**\n * Create a spy that tracks function calls\n *\n * Useful for testing side effects without full mocking\n */\nexport function createSpy<T extends (...args: unknown[]) => unknown>(\n _name = 'spy'\n): Mock<T> & { getCalls(): unknown[][]; getLastCall(): unknown[] } {\n const calls: unknown[][] = [];\n\n const spy = vi.fn((...args: unknown[]) => {\n calls.push(args);\n }) as Mock<T> & { getCalls(): unknown[][]; getLastCall(): unknown[] };\n\n spy.getCalls = () => calls;\n spy.getLastCall = () => calls[calls.length - 1] || [];\n\n return spy;\n}\n\n/**\n * Wait for a condition to be true\n *\n * Useful for async testing\n */\nexport async function waitFor(\n condition: () => boolean | Promise<boolean>,\n options: { timeout?: number; interval?: number } = {}\n): Promise<void> {\n const { timeout = 5000, interval = 100 } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n if (await condition()) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n\n throw new Error(`Timeout waiting for condition after ${timeout}ms`);\n}\n\n/**\n * Create a test timer that can be controlled\n */\nexport function createTestTimer() {\n let time = Date.now();\n\n return {\n now: () => time,\n advance: (ms: number) => {\n time += ms;\n },\n set: (timestamp: number) => {\n time = timestamp;\n },\n reset: () => {\n time = Date.now();\n },\n };\n}\n","/**\r\n * Better Auth Test Helpers\r\n *\r\n * Reusable primitives for testing Arc apps that use Better Auth.\r\n * Extracted from common patterns in app-level test setups.\r\n *\r\n * @example Basic helpers\r\n * ```typescript\r\n * import { createBetterAuthTestHelpers } from '@classytic/arc/testing';\r\n *\r\n * const auth = createBetterAuthTestHelpers();\r\n * const signup = await auth.signUp(app, { email: 'test@example.com', password: 'pass', name: 'Test' });\r\n * const headers = auth.authHeaders(signup.token, orgId);\r\n * ```\r\n *\r\n * @example Full org setup\r\n * ```typescript\r\n * import { setupBetterAuthOrg } from '@classytic/arc/testing';\r\n *\r\n * const ctx = await setupBetterAuthOrg({\r\n * createApp: () => createAppInstance(),\r\n * org: { name: 'Test Corp', slug: 'test-corp' },\r\n * users: [\r\n * { key: 'admin', email: 'admin@test.com', password: 'pass', name: 'Admin', role: 'admin', isCreator: true },\r\n * { key: 'member', email: 'user@test.com', password: 'pass', name: 'User', role: 'member' },\r\n * ],\r\n * addMember: async (data) => { await auth.api.addMember({ body: data }); return { statusCode: 200 }; },\r\n * });\r\n *\r\n * // ctx.app, ctx.orgId, ctx.users.admin.token, ctx.teardown()\r\n * ```\r\n */\r\n\r\nimport type { FastifyInstance } from 'fastify';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface BetterAuthTestHelpersOptions {\r\n /** Base path for auth routes (default: '/api/auth') */\r\n basePath?: string;\r\n}\r\n\r\nexport interface AuthResponse {\r\n statusCode: number;\r\n token: string;\r\n user: any;\r\n body: any;\r\n}\r\n\r\nexport interface OrgResponse {\r\n statusCode: number;\r\n orgId: string;\r\n body: any;\r\n}\r\n\r\nexport interface BetterAuthTestHelpers {\r\n signUp(app: FastifyInstance, data: { email: string; password: string; name: string }): Promise<AuthResponse>;\r\n signIn(app: FastifyInstance, data: { email: string; password: string }): Promise<AuthResponse>;\r\n createOrg(app: FastifyInstance, token: string, data: { name: string; slug: string }): Promise<OrgResponse>;\r\n setActiveOrg(app: FastifyInstance, token: string, orgId: string): Promise<{ statusCode: number; body: any }>;\r\n authHeaders(token: string, orgId?: string): Record<string, string>;\r\n}\r\n\r\nexport interface TestUserContext {\r\n token: string;\r\n userId: string;\r\n email: string;\r\n}\r\n\r\nexport interface TestOrgContext<T = Record<string, TestUserContext>> {\r\n app: FastifyInstance;\r\n orgId: string;\r\n users: T;\r\n teardown: () => Promise<void>;\r\n}\r\n\r\nexport interface SetupUserConfig {\r\n /** Key used to reference this user in the context (e.g. 'admin', 'member') */\r\n key: string;\r\n email: string;\r\n password: string;\r\n name: string;\r\n /** Organization role assigned after joining */\r\n role: string;\r\n /** If true, this user creates the org (becomes org owner). Exactly one user should have this. */\r\n isCreator?: boolean;\r\n}\r\n\r\nexport interface SetupBetterAuthOrgOptions {\r\n /** Factory function to create the Fastify app instance */\r\n createApp: () => Promise<FastifyInstance>;\r\n /** Organization to create */\r\n org: { name: string; slug: string };\r\n /** Users to create and add to the organization */\r\n users: SetupUserConfig[];\r\n /**\r\n * Callback to add a member to the org.\r\n * Apps wire Better Auth differently — some use auth.api.addMember, others use HTTP.\r\n */\r\n addMember: (data: { organizationId: string; userId: string; role: string }) => Promise<{ statusCode: number }>;\r\n /**\r\n * Optional hook for app-specific initialization after all users are set up.\r\n * Use this for things like recruiter→account manager hierarchy.\r\n */\r\n afterSetup?: (ctx: TestOrgContext) => Promise<void>;\r\n /** Override auth helper options (e.g. custom basePath) */\r\n authHelpers?: BetterAuthTestHelpersOptions;\r\n}\r\n\r\n// ============================================================================\r\n// Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Safely parse a JSON response body.\r\n * Returns null if parsing fails.\r\n */\r\nexport function safeParseBody(body: string): any {\r\n try {\r\n return JSON.parse(body);\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Better Auth Test Helpers Factory\r\n// ============================================================================\r\n\r\n/**\r\n * Create stateless Better Auth test helpers.\r\n *\r\n * All methods take the app instance as a parameter, making them\r\n * safe to use across multiple test suites.\r\n */\r\nexport function createBetterAuthTestHelpers(\r\n options: BetterAuthTestHelpersOptions = {},\r\n): BetterAuthTestHelpers {\r\n const basePath = options.basePath ?? '/api/auth';\r\n\r\n return {\r\n async signUp(app, data) {\r\n const res = await app.inject({\r\n method: 'POST',\r\n url: `${basePath}/sign-up/email`,\r\n payload: data,\r\n });\r\n const token = res.headers['set-auth-token'] as string | undefined;\r\n const body = safeParseBody(res.body);\r\n return {\r\n statusCode: res.statusCode,\r\n token: token || '',\r\n user: body?.user || body,\r\n body,\r\n };\r\n },\r\n\r\n async signIn(app, data) {\r\n const res = await app.inject({\r\n method: 'POST',\r\n url: `${basePath}/sign-in/email`,\r\n payload: data,\r\n });\r\n const token = res.headers['set-auth-token'] as string | undefined;\r\n const body = safeParseBody(res.body);\r\n return {\r\n statusCode: res.statusCode,\r\n token: token || '',\r\n user: body?.user || body,\r\n body,\r\n };\r\n },\r\n\r\n async createOrg(app, token, data) {\r\n const res = await app.inject({\r\n method: 'POST',\r\n url: `${basePath}/organization/create`,\r\n headers: { authorization: `Bearer ${token}` },\r\n payload: data,\r\n });\r\n const body = safeParseBody(res.body);\r\n return {\r\n statusCode: res.statusCode,\r\n orgId: body?.id,\r\n body,\r\n };\r\n },\r\n\r\n async setActiveOrg(app, token, orgId) {\r\n const res = await app.inject({\r\n method: 'POST',\r\n url: `${basePath}/organization/set-active`,\r\n headers: { authorization: `Bearer ${token}` },\r\n payload: { organizationId: orgId },\r\n });\r\n return {\r\n statusCode: res.statusCode,\r\n body: safeParseBody(res.body),\r\n };\r\n },\r\n\r\n authHeaders(token, orgId?) {\r\n const h: Record<string, string> = { authorization: `Bearer ${token}` };\r\n if (orgId) h['x-organization-id'] = orgId;\r\n return h;\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Composite Org Setup\r\n// ============================================================================\r\n\r\n/**\r\n * Set up a complete test organization with users.\r\n *\r\n * Creates the app, signs up users, creates an org, adds members,\r\n * and returns a context object with tokens and a teardown function.\r\n *\r\n * @example\r\n * ```typescript\r\n * const ctx = await setupBetterAuthOrg({\r\n * createApp: () => createAppInstance(),\r\n * org: { name: 'Test Corp', slug: 'test-corp' },\r\n * users: [\r\n * { key: 'admin', email: 'admin@test.com', password: 'pass', name: 'Admin', role: 'admin', isCreator: true },\r\n * { key: 'member', email: 'user@test.com', password: 'pass', name: 'User', role: 'member' },\r\n * ],\r\n * addMember: async (data) => {\r\n * await auth.api.addMember({ body: data });\r\n * return { statusCode: 200 };\r\n * },\r\n * });\r\n *\r\n * // Use in tests:\r\n * const res = await ctx.app.inject({\r\n * method: 'GET',\r\n * url: '/api/products',\r\n * headers: auth.authHeaders(ctx.users.admin.token, ctx.orgId),\r\n * });\r\n *\r\n * // Cleanup:\r\n * await ctx.teardown();\r\n * ```\r\n */\r\nexport async function setupBetterAuthOrg(\r\n options: SetupBetterAuthOrgOptions,\r\n): Promise<TestOrgContext> {\r\n const {\r\n createApp,\r\n org,\r\n users: userConfigs,\r\n addMember,\r\n afterSetup,\r\n authHelpers: helpersOptions,\r\n } = options;\r\n\r\n const helpers = createBetterAuthTestHelpers(helpersOptions);\r\n\r\n // Validate: exactly one creator\r\n const creators = userConfigs.filter((u) => u.isCreator);\r\n if (creators.length !== 1) {\r\n throw new Error(\r\n `setupBetterAuthOrg: Exactly one user must have isCreator: true (found ${creators.length})`,\r\n );\r\n }\r\n\r\n // 1. Create app\r\n const app = await createApp();\r\n await app.ready();\r\n\r\n // 2. Sign up all users\r\n const signups = new Map<string, AuthResponse>();\r\n for (const userConfig of userConfigs) {\r\n const signup = await helpers.signUp(app, {\r\n email: userConfig.email,\r\n password: userConfig.password,\r\n name: userConfig.name,\r\n });\r\n if (signup.statusCode !== 200) {\r\n throw new Error(\r\n `setupBetterAuthOrg: Failed to sign up ${userConfig.email} (status ${signup.statusCode})`,\r\n );\r\n }\r\n signups.set(userConfig.key, signup);\r\n }\r\n\r\n // 3. Create org (by the creator)\r\n const creatorConfig = creators[0]!;\r\n const creatorSignup = signups.get(creatorConfig.key)!;\r\n const orgResult = await helpers.createOrg(app, creatorSignup.token, org);\r\n if (orgResult.statusCode !== 200) {\r\n throw new Error(\r\n `setupBetterAuthOrg: Failed to create org (status ${orgResult.statusCode})`,\r\n );\r\n }\r\n const orgId = orgResult.orgId;\r\n\r\n // 4. Add non-creator members\r\n for (const userConfig of userConfigs) {\r\n if (userConfig.isCreator) continue;\r\n const signup = signups.get(userConfig.key)!;\r\n const result = await addMember({\r\n organizationId: orgId,\r\n userId: signup.user?.id,\r\n role: userConfig.role,\r\n });\r\n if (result.statusCode !== 200) {\r\n throw new Error(\r\n `setupBetterAuthOrg: Failed to add member ${userConfig.email} (status ${result.statusCode})`,\r\n );\r\n }\r\n }\r\n\r\n // 5. Set active org + re-login to get fresh tokens\r\n await helpers.setActiveOrg(app, creatorSignup.token, orgId);\r\n\r\n const users: Record<string, TestUserContext> = {};\r\n for (const userConfig of userConfigs) {\r\n if (userConfig.isCreator) {\r\n const signup = signups.get(userConfig.key)!;\r\n users[userConfig.key] = {\r\n token: signup.token,\r\n userId: signup.user?.id,\r\n email: userConfig.email,\r\n };\r\n } else {\r\n // Re-login to get token with org context\r\n const login = await helpers.signIn(app, {\r\n email: userConfig.email,\r\n password: userConfig.password,\r\n });\r\n await helpers.setActiveOrg(app, login.token, orgId);\r\n users[userConfig.key] = {\r\n token: login.token,\r\n userId: signups.get(userConfig.key)!.user?.id,\r\n email: userConfig.email,\r\n };\r\n }\r\n }\r\n\r\n const ctx: TestOrgContext = {\r\n app,\r\n orgId,\r\n users,\r\n async teardown() {\r\n await app.close();\r\n },\r\n };\r\n\r\n // 6. Run app-specific post-setup\r\n if (afterSetup) {\r\n await afterSetup(ctx);\r\n }\r\n\r\n return ctx;\r\n}\r\n","/**\r\n * HTTP Test Harness\r\n *\r\n * Generates HTTP-level CRUD tests for Arc resources using `app.inject()`.\r\n * Unlike TestHarness (which tests Mongoose models directly), this exercises\r\n * the full request lifecycle: HTTP routes, auth, permissions, pipeline,\r\n * field permissions, and the Arc response envelope.\r\n *\r\n * Supports both eager and deferred options:\r\n * - **Eager**: Pass options directly when app is available at construction time\r\n * - **Deferred**: Pass a getter function when app comes from async setup (beforeAll)\r\n *\r\n * @example Eager (app available at module level)\r\n * ```typescript\r\n * const harness = createHttpTestHarness(jobResource, {\r\n * app,\r\n * fixtures: { valid: { title: 'Test' } },\r\n * auth: createJwtAuthProvider({ app, users, adminRole: 'admin' }),\r\n * });\r\n * harness.runAll();\r\n * ```\r\n *\r\n * @example Deferred (app from beforeAll)\r\n * ```typescript\r\n * let ctx: TestContext;\r\n * beforeAll(async () => { ctx = await setupTestOrg(); });\r\n * afterAll(async () => { await teardownTestOrg(ctx); });\r\n *\r\n * const harness = createHttpTestHarness(jobResource, () => ({\r\n * app: ctx.app,\r\n * fixtures: { valid: { title: 'Test' } },\r\n * auth: createBetterAuthProvider({ tokens: { admin: ctx.users.admin.token }, orgId: ctx.orgId, adminRole: 'admin' }),\r\n * }));\r\n * harness.runAll();\r\n * ```\r\n */\r\n\r\nimport { describe, it, expect, afterAll } from 'vitest';\r\nimport type { FastifyInstance } from 'fastify';\r\nimport type { ResourceDefinition } from '../core/defineResource.js';\r\nimport { CRUD_OPERATIONS } from '../constants.js';\r\n\r\n// ============================================================================\r\n// Auth Provider Interface\r\n// ============================================================================\r\n\r\n/**\r\n * Abstraction for generating auth headers in tests.\r\n * Supports JWT, Better Auth, or any custom auth mechanism.\r\n */\r\nexport interface AuthProvider {\r\n /** Get HTTP headers for a given role key */\r\n getHeaders(role: string): Record<string, string>;\r\n /** Available role keys (e.g. ['admin', 'member', 'viewer']) */\r\n availableRoles: string[];\r\n /** Role key that has full CRUD access */\r\n adminRole: string;\r\n}\r\n\r\n// ============================================================================\r\n// Auth Provider Factories\r\n// ============================================================================\r\n\r\n/**\r\n * Create an auth provider for JWT-based apps.\r\n *\r\n * Generates JWT tokens on the fly using the app's JWT plugin.\r\n *\r\n * @example\r\n * ```typescript\r\n * const auth = createJwtAuthProvider({\r\n * app,\r\n * users: {\r\n * admin: { payload: { id: '1', roles: ['admin'] }, organizationId: 'org1' },\r\n * viewer: { payload: { id: '2', roles: ['viewer'] } },\r\n * },\r\n * adminRole: 'admin',\r\n * });\r\n * ```\r\n */\r\nexport function createJwtAuthProvider(options: {\r\n app: FastifyInstance;\r\n users: Record<string, { payload: Record<string, unknown>; organizationId?: string }>;\r\n adminRole: string;\r\n}): AuthProvider {\r\n const { app, users, adminRole } = options;\r\n\r\n return {\r\n getHeaders(role: string): Record<string, string> {\r\n const user = users[role];\r\n if (!user) {\r\n throw new Error(`createJwtAuthProvider: Unknown role '${role}'. Available: ${Object.keys(users).join(', ')}`);\r\n }\r\n const token = (app as any).jwt?.sign?.(user.payload) || 'mock-token';\r\n const headers: Record<string, string> = {\r\n authorization: `Bearer ${token}`,\r\n };\r\n if (user.organizationId) {\r\n headers['x-organization-id'] = user.organizationId;\r\n }\r\n return headers;\r\n },\r\n availableRoles: Object.keys(users),\r\n adminRole,\r\n };\r\n}\r\n\r\n/**\r\n * Create an auth provider for Better Auth apps.\r\n *\r\n * Uses pre-existing tokens (from signUp/signIn) rather than generating them.\r\n *\r\n * @example\r\n * ```typescript\r\n * const auth = createBetterAuthProvider({\r\n * tokens: {\r\n * admin: ctx.users.admin.token,\r\n * member: ctx.users.member.token,\r\n * },\r\n * orgId: ctx.orgId,\r\n * adminRole: 'admin',\r\n * });\r\n * ```\r\n */\r\nexport function createBetterAuthProvider(options: {\r\n tokens: Record<string, string>;\r\n orgId: string;\r\n adminRole: string;\r\n}): AuthProvider {\r\n const { tokens, orgId, adminRole } = options;\r\n\r\n return {\r\n getHeaders(role: string): Record<string, string> {\r\n const token = tokens[role];\r\n if (!token) {\r\n throw new Error(`createBetterAuthProvider: No token for role '${role}'. Available: ${Object.keys(tokens).join(', ')}`);\r\n }\r\n return {\r\n authorization: `Bearer ${token}`,\r\n 'x-organization-id': orgId,\r\n };\r\n },\r\n availableRoles: Object.keys(tokens),\r\n adminRole,\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// HTTP Test Harness\r\n// ============================================================================\r\n\r\nexport interface HttpTestHarnessOptions<T = unknown> {\r\n /** Fastify app instance (must be ready) */\r\n app: FastifyInstance;\r\n /** Test data fixtures */\r\n fixtures: {\r\n /** Valid payload for creating a resource */\r\n valid: Partial<T>;\r\n /** Payload for updating a resource (defaults to valid) */\r\n update?: Partial<T>;\r\n /** Invalid payload that should fail validation */\r\n invalid?: Partial<T>;\r\n };\r\n /** Auth provider for generating request headers */\r\n auth: AuthProvider;\r\n /** API path prefix (default: '/api') */\r\n apiPrefix?: string;\r\n}\r\n\r\n/** Options can be passed directly or as a getter for deferred resolution */\r\ntype OptionsOrGetter<T> = HttpTestHarnessOptions<T> | (() => HttpTestHarnessOptions<T>);\r\n\r\n/**\r\n * HTTP-level test harness for Arc resources.\r\n *\r\n * Generates tests that exercise the full HTTP lifecycle:\r\n * routes, auth, permissions, pipeline, and response envelope.\r\n *\r\n * Supports deferred options via a getter function, which is essential\r\n * when the app instance comes from async `beforeAll()` setup.\r\n */\r\nexport class HttpTestHarness<T = unknown> {\r\n private resource: ResourceDefinition<unknown>;\r\n private optionsOrGetter: OptionsOrGetter<T>;\r\n private baseUrl: string;\r\n private enabledRoutes: Set<string>;\r\n private updateMethod: string;\r\n\r\n constructor(resource: ResourceDefinition<unknown>, optionsOrGetter: OptionsOrGetter<T>) {\r\n this.resource = resource;\r\n this.optionsOrGetter = optionsOrGetter;\r\n\r\n // These come from the resource definition (available at import time, no async needed)\r\n const apiPrefix = typeof optionsOrGetter === 'function'\r\n ? '/api'\r\n : (optionsOrGetter.apiPrefix ?? '/api');\r\n this.baseUrl = `${apiPrefix}${resource.prefix}`;\r\n\r\n // Determine which CRUD routes are enabled (from resource, not options)\r\n const disabled = new Set(resource.disabledRoutes ?? []);\r\n this.enabledRoutes = new Set(\r\n resource.disableDefaultRoutes\r\n ? []\r\n : CRUD_OPERATIONS.filter((op) => !disabled.has(op)),\r\n );\r\n\r\n this.updateMethod = resource.updateMethod === 'PUT' ? 'PUT' : 'PATCH';\r\n }\r\n\r\n /** Resolve options (supports both direct and deferred) */\r\n private getOptions(): HttpTestHarnessOptions<T> {\r\n return typeof this.optionsOrGetter === 'function'\r\n ? this.optionsOrGetter()\r\n : this.optionsOrGetter;\r\n }\r\n\r\n /**\r\n * Run all test suites: CRUD + permissions + validation\r\n */\r\n runAll(): void {\r\n this.runCrud();\r\n this.runPermissions();\r\n this.runValidation();\r\n }\r\n\r\n /**\r\n * Run HTTP-level CRUD tests.\r\n *\r\n * Tests each enabled CRUD operation through app.inject():\r\n * - POST (create) → 200/201 with { success: true, data }\r\n * - GET (list) → 200 with array or paginated response\r\n * - GET /:id → 200 with { success: true, data }\r\n * - PATCH/PUT /:id → 200 with { success: true, data }\r\n * - DELETE /:id → 200\r\n * - GET /:id with non-existent ID → 404\r\n */\r\n runCrud(): void {\r\n const { resource, baseUrl, enabledRoutes, updateMethod } = this;\r\n\r\n // Track created IDs for cleanup and cross-test references\r\n let createdId: string | null = null;\r\n\r\n describe(`${resource.displayName} HTTP CRUD`, () => {\r\n afterAll(async () => {\r\n // Cleanup: delete the created resource if still exists\r\n if (createdId && enabledRoutes.has('delete')) {\r\n const { app, auth } = this.getOptions();\r\n await app.inject({\r\n method: 'DELETE',\r\n url: `${baseUrl}/${createdId}`,\r\n headers: auth.getHeaders(auth.adminRole),\r\n });\r\n }\r\n });\r\n\r\n if (enabledRoutes.has('create')) {\r\n it('POST should create a resource', async () => {\r\n const { app, auth, fixtures } = this.getOptions();\r\n const adminHeaders = auth.getHeaders(auth.adminRole);\r\n\r\n const res = await app.inject({\r\n method: 'POST',\r\n url: baseUrl,\r\n headers: adminHeaders,\r\n payload: fixtures.valid,\r\n });\r\n\r\n expect(res.statusCode).toBeLessThan(300);\r\n const body = JSON.parse(res.body);\r\n expect(body.success).toBe(true);\r\n expect(body.data).toBeDefined();\r\n expect(body.data._id).toBeDefined();\r\n\r\n // Store for subsequent tests\r\n createdId = body.data._id;\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('list')) {\r\n it('GET should list resources', async () => {\r\n const { app, auth } = this.getOptions();\r\n\r\n const res = await app.inject({\r\n method: 'GET',\r\n url: baseUrl,\r\n headers: auth.getHeaders(auth.adminRole),\r\n });\r\n\r\n expect(res.statusCode).toBe(200);\r\n const body = JSON.parse(res.body);\r\n expect(body.success).toBe(true);\r\n // Arc list responses use `data` or `docs` depending on the query parser\r\n const list = body.data ?? body.docs;\r\n expect(list).toBeDefined();\r\n expect(Array.isArray(list)).toBe(true);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('get')) {\r\n it('GET /:id should return the resource', async () => {\r\n if (!createdId) return;\r\n\r\n const { app, auth } = this.getOptions();\r\n const res = await app.inject({\r\n method: 'GET',\r\n url: `${baseUrl}/${createdId}`,\r\n headers: auth.getHeaders(auth.adminRole),\r\n });\r\n\r\n expect(res.statusCode).toBe(200);\r\n const body = JSON.parse(res.body);\r\n expect(body.success).toBe(true);\r\n expect(body.data).toBeDefined();\r\n expect(body.data._id).toBe(createdId);\r\n });\r\n\r\n it('GET /:id with non-existent ID should return 404', async () => {\r\n const { app, auth } = this.getOptions();\r\n const fakeId = '000000000000000000000000';\r\n const res = await app.inject({\r\n method: 'GET',\r\n url: `${baseUrl}/${fakeId}`,\r\n headers: auth.getHeaders(auth.adminRole),\r\n });\r\n\r\n expect(res.statusCode).toBe(404);\r\n const body = JSON.parse(res.body);\r\n expect(body.success).toBe(false);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('update')) {\r\n it(`${updateMethod} /:id should update the resource`, async () => {\r\n if (!createdId) return;\r\n\r\n const { app, auth, fixtures } = this.getOptions();\r\n const updatePayload = fixtures.update || fixtures.valid;\r\n const res = await app.inject({\r\n method: updateMethod as any,\r\n url: `${baseUrl}/${createdId}`,\r\n headers: auth.getHeaders(auth.adminRole),\r\n payload: updatePayload,\r\n });\r\n\r\n expect(res.statusCode).toBe(200);\r\n const body = JSON.parse(res.body);\r\n expect(body.success).toBe(true);\r\n expect(body.data).toBeDefined();\r\n });\r\n\r\n it(`${updateMethod} /:id with non-existent ID should return 404`, async () => {\r\n const { app, auth, fixtures } = this.getOptions();\r\n const fakeId = '000000000000000000000000';\r\n const res = await app.inject({\r\n method: updateMethod as any,\r\n url: `${baseUrl}/${fakeId}`,\r\n headers: auth.getHeaders(auth.adminRole),\r\n payload: fixtures.update || fixtures.valid,\r\n });\r\n\r\n expect(res.statusCode).toBe(404);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('delete')) {\r\n it('DELETE /:id should delete the resource', async () => {\r\n const { app, auth, fixtures } = this.getOptions();\r\n const adminHeaders = auth.getHeaders(auth.adminRole);\r\n\r\n // Create a separate resource for deletion to avoid affecting other tests\r\n let deleteId: string | undefined;\r\n\r\n if (enabledRoutes.has('create')) {\r\n const createRes = await app.inject({\r\n method: 'POST',\r\n url: baseUrl,\r\n headers: adminHeaders,\r\n payload: fixtures.valid,\r\n });\r\n deleteId = JSON.parse(createRes.body).data?._id;\r\n }\r\n\r\n if (!deleteId) return;\r\n\r\n const res = await app.inject({\r\n method: 'DELETE',\r\n url: `${baseUrl}/${deleteId}`,\r\n headers: adminHeaders,\r\n });\r\n\r\n expect(res.statusCode).toBe(200);\r\n\r\n // Verify it's gone\r\n if (enabledRoutes.has('get')) {\r\n const getRes = await app.inject({\r\n method: 'GET',\r\n url: `${baseUrl}/${deleteId}`,\r\n headers: adminHeaders,\r\n });\r\n expect(getRes.statusCode).toBe(404);\r\n }\r\n });\r\n\r\n it('DELETE /:id with non-existent ID should return 404', async () => {\r\n const { app, auth } = this.getOptions();\r\n const fakeId = '000000000000000000000000';\r\n const res = await app.inject({\r\n method: 'DELETE',\r\n url: `${baseUrl}/${fakeId}`,\r\n headers: auth.getHeaders(auth.adminRole),\r\n });\r\n\r\n expect(res.statusCode).toBe(404);\r\n });\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Run permission tests.\r\n *\r\n * Tests that:\r\n * - Unauthenticated requests return 401\r\n * - Admin role gets 2xx for all operations\r\n */\r\n runPermissions(): void {\r\n const { resource, baseUrl, enabledRoutes, updateMethod } = this;\r\n\r\n describe(`${resource.displayName} HTTP Permissions`, () => {\r\n if (enabledRoutes.has('list')) {\r\n it('GET list without auth should return 401', async () => {\r\n const { app } = this.getOptions();\r\n const res = await app.inject({ method: 'GET', url: baseUrl });\r\n expect(res.statusCode).toBe(401);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('get')) {\r\n it('GET get without auth should return 401', async () => {\r\n const { app } = this.getOptions();\r\n const res = await app.inject({ method: 'GET', url: `${baseUrl}/000000000000000000000000` });\r\n expect(res.statusCode).toBe(401);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('create')) {\r\n it('POST create without auth should return 401', async () => {\r\n const { app, fixtures } = this.getOptions();\r\n const res = await app.inject({ method: 'POST', url: baseUrl, payload: fixtures.valid });\r\n expect(res.statusCode).toBe(401);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('update')) {\r\n it(`${updateMethod} update without auth should return 401`, async () => {\r\n const { app, fixtures } = this.getOptions();\r\n const res = await app.inject({\r\n method: updateMethod as any,\r\n url: `${baseUrl}/000000000000000000000000`,\r\n payload: fixtures.update || fixtures.valid,\r\n });\r\n expect(res.statusCode).toBe(401);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('delete')) {\r\n it('DELETE delete without auth should return 401', async () => {\r\n const { app } = this.getOptions();\r\n const res = await app.inject({ method: 'DELETE', url: `${baseUrl}/000000000000000000000000` });\r\n expect(res.statusCode).toBe(401);\r\n });\r\n }\r\n\r\n // Admin access tests\r\n if (enabledRoutes.has('list')) {\r\n it('admin should access list endpoint', async () => {\r\n const { app, auth } = this.getOptions();\r\n const res = await app.inject({\r\n method: 'GET',\r\n url: baseUrl,\r\n headers: auth.getHeaders(auth.adminRole),\r\n });\r\n expect(res.statusCode).toBeLessThan(400);\r\n });\r\n }\r\n\r\n if (enabledRoutes.has('create')) {\r\n it('admin should access create endpoint', async () => {\r\n const { app, auth, fixtures } = this.getOptions();\r\n const res = await app.inject({\r\n method: 'POST',\r\n url: baseUrl,\r\n headers: auth.getHeaders(auth.adminRole),\r\n payload: fixtures.valid,\r\n });\r\n expect(res.statusCode).toBeLessThan(400);\r\n\r\n // Cleanup\r\n const body = JSON.parse(res.body);\r\n if (body.data?._id && enabledRoutes.has('delete')) {\r\n await app.inject({\r\n method: 'DELETE',\r\n url: `${baseUrl}/${body.data._id}`,\r\n headers: auth.getHeaders(auth.adminRole),\r\n });\r\n }\r\n });\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Run validation tests.\r\n *\r\n * Tests that invalid payloads return 400.\r\n */\r\n runValidation(): void {\r\n const { resource, baseUrl, enabledRoutes } = this;\r\n\r\n if (!enabledRoutes.has('create')) return;\r\n\r\n describe(`${resource.displayName} HTTP Validation`, () => {\r\n it('POST with invalid payload should not return 2xx', async () => {\r\n const { app, auth, fixtures } = this.getOptions();\r\n if (!fixtures.invalid) return;\r\n\r\n const res = await app.inject({\r\n method: 'POST',\r\n url: baseUrl,\r\n headers: auth.getHeaders(auth.adminRole),\r\n payload: fixtures.invalid,\r\n });\r\n\r\n // Invalid payload should be rejected — 400 (schema validation) or\r\n // 422/500 (model validation) depending on whether JSON Schema is configured\r\n expect(res.statusCode).toBeGreaterThanOrEqual(400);\r\n const body = JSON.parse(res.body);\r\n expect(body.success).toBe(false);\r\n });\r\n });\r\n }\r\n}\r\n\r\n/**\r\n * Create an HTTP test harness for an Arc resource.\r\n *\r\n * Accepts options directly or as a getter function for deferred resolution.\r\n *\r\n * @example Deferred (recommended for async setup)\r\n * ```typescript\r\n * let ctx: TestContext;\r\n * beforeAll(async () => { ctx = await setupTestOrg(); });\r\n *\r\n * createHttpTestHarness(jobResource, () => ({\r\n * app: ctx.app,\r\n * fixtures: { valid: { title: 'Test' } },\r\n * auth: createBetterAuthProvider({ ... }),\r\n * })).runAll();\r\n * ```\r\n */\r\nexport function createHttpTestHarness<T = unknown>(\r\n resource: ResourceDefinition<unknown>,\r\n optionsOrGetter: HttpTestHarnessOptions<T> | (() => HttpTestHarnessOptions<T>),\r\n): HttpTestHarness<T> {\r\n return new HttpTestHarness<T>(resource, optionsOrGetter);\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyRequest } from "fastify";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/tracing.d.ts
|
|
4
|
+
interface TracingOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Service name for traces
|
|
7
|
+
*/
|
|
8
|
+
serviceName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* OTLP exporter endpoint URL
|
|
11
|
+
* @default 'http://localhost:4318/v1/traces'
|
|
12
|
+
*/
|
|
13
|
+
exporterUrl?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Enable auto-instrumentation for HTTP, MongoDB, etc.
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
autoInstrumentation?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Sample rate (0.0 to 1.0)
|
|
21
|
+
* @default 1.0 (trace everything)
|
|
22
|
+
*/
|
|
23
|
+
sampleRate?: number;
|
|
24
|
+
}
|
|
25
|
+
interface TracerContext {
|
|
26
|
+
tracer: any;
|
|
27
|
+
currentSpan: any;
|
|
28
|
+
}
|
|
29
|
+
declare module 'fastify' {
|
|
30
|
+
interface FastifyRequest {
|
|
31
|
+
tracer?: TracerContext;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* OpenTelemetry Distributed Tracing Plugin
|
|
36
|
+
*/
|
|
37
|
+
declare function tracingPlugin(fastify: FastifyInstance, options?: TracingOptions): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Utility to create custom spans in your code
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* import { createSpan } from '@classytic/arc/plugins';
|
|
43
|
+
*
|
|
44
|
+
* async function expensiveOperation(req) {
|
|
45
|
+
* return createSpan(req, 'expensiveOperation', async (span) => {
|
|
46
|
+
* span.setAttribute('custom.attribute', 'value');
|
|
47
|
+
* return await doWork();
|
|
48
|
+
* });
|
|
49
|
+
* }
|
|
50
|
+
*/
|
|
51
|
+
declare function createSpan<T>(request: FastifyRequest, name: string, fn: (span: any) => Promise<T>, attributes?: Record<string, any>): Promise<T>;
|
|
52
|
+
/**
|
|
53
|
+
* Decorator to automatically trace repository methods
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* class ProductRepository extends Repository {
|
|
57
|
+
* @traced()
|
|
58
|
+
* async findActive() {
|
|
59
|
+
* return this.findAll({ filter: { isActive: true } });
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
declare function traced(spanName?: string): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
64
|
+
/**
|
|
65
|
+
* Check if OpenTelemetry is available
|
|
66
|
+
*/
|
|
67
|
+
declare function isTracingAvailable(): boolean;
|
|
68
|
+
declare const _default: typeof tracingPlugin;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { traced as a, isTracingAvailable as i, _default as n, createSpan as r, TracingOptions as t };
|
|
71
|
+
//# sourceMappingURL=tracing-Cc7vVQPp.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing-Cc7vVQPp.d.mts","names":[],"sources":["../src/plugins/tracing.ts"],"mappings":";;;UA6DiB,cAAA;EA0Bf;;AACW;EAvBX,WAAA;;;;;EAMA,WAAA;EAsBwB;;;AAAA;EAhBxB,mBAAA;;;;;EAMA,UAAA;AAAA;AAAA,UAGQ,aAAA;EACR,MAAA;EACA,WAAA;AAAA;AAAA;EAAA,UAIU,cAAA;IACR,MAAA,GAAS,aAAA;EAAA;AAAA;;;;iBAqCE,aAAA,CAAc,OAAA,EAAS,eAAA,EAAiB,OAAA,GAAS,cAAA,GAAmB,OAAA;;;;;;;;;;;;;;iBAiInE,UAAA,GAAA,CACd,OAAA,EAAS,cAAA,EACT,IAAA,UACA,EAAA,GAAK,IAAA,UAAc,OAAA,CAAQ,CAAA,GAC3B,UAAA,GAAa,MAAA,gBACZ,OAAA,CAAQ,CAAA;;;;;;AA8CX;;;;;;iBAAgB,MAAA,CAAO,QAAA,aACJ,MAAA,OAAa,WAAA,UAAqB,UAAA,EAAY,kBAAA,KAAkB,kBAAA;;;;iBA6BnE,kBAAA,CAAA;AAAA,cAEf,QAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/utils/typeGuards.ts
|
|
2
|
+
/** Check if fastify has the events plugin registered */
|
|
3
|
+
function hasEvents(instance) {
|
|
4
|
+
const inst = instance;
|
|
5
|
+
return inst.events != null && typeof inst.events.publish === "function";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
export { hasEvents as t };
|
|
10
|
+
//# sourceMappingURL=typeGuards-DhMNLuvU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeGuards-DhMNLuvU.mjs","names":[],"sources":["../src/utils/typeGuards.ts"],"sourcesContent":["/**\r\n * Shared Type Guards\r\n *\r\n * Reusable type narrowing for Fastify plugin decorators.\r\n * Eliminates inline `'events' in fastify && ...` checks across the codebase.\r\n */\r\n\r\nimport type { FastifyInstance } from 'fastify';\r\n\r\nexport interface EventsDecorator {\r\n publish: <T>(type: string, payload: T, meta?: Record<string, unknown>) => Promise<void>;\r\n subscribe: (pattern: string, handler: (event: { type: string; payload: unknown; meta: Record<string, unknown> }) => Promise<void>) => Promise<() => void>;\r\n transportName: string;\r\n}\r\n\r\n/** Check if fastify has the events plugin registered */\r\nexport function hasEvents(instance: FastifyInstance): instance is FastifyInstance & { events: EventsDecorator } {\r\n const inst = instance as unknown as Record<string, unknown>;\r\n return inst.events != null && typeof (inst.events as Record<string, unknown>).publish === 'function';\r\n}\r\n"],"mappings":";;AAgBA,SAAgB,UAAU,UAAsF;CAC9G,MAAM,OAAO;AACb,QAAO,KAAK,UAAU,QAAQ,OAAQ,KAAK,OAAmC,YAAY"}
|