@classytic/arc 2.41.1 → 2.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
- package/dist/{EventTransport-DSqD_Xsg.mjs → EventTransport-DZWDsaPa.mjs} +22 -17
- package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-yjEgn7Cm.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-DJxHUpE4.mjs} +44 -13
- package/dist/acquire-BozJ-9yt.mjs +35 -0
- package/dist/actionPermissions-5N04zBL4.mjs +772 -0
- package/dist/audit/index.d.mts +46 -8
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.d.mts +8 -8
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +46 -16
- package/dist/auth/index.mjs +134 -156
- package/dist/auth/redis-session.d.mts +4 -5
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-wX3SA9w_.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-DWiKW7lP.mjs +57 -0
- package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-Bswu_vNb.mjs} +4 -4
- package/dist/cache/index.d.mts +31 -43
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DbCBxlgw.mjs → caching-Drp68DIS.mjs} +3 -3
- package/dist/claimed-DYvQoZLv.mjs +8 -0
- package/dist/cleanup/index.d.mts +18 -22
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +7 -5
- package/dist/cli/commands/describe.mjs +7 -4
- package/dist/cli/commands/docs.d.mts +2 -3
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.d.mts +2 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -3
- package/dist/cli/commands/init.mjs +2 -3
- package/dist/cli/commands/introspect.d.mts +2 -3
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D5Ubn1fe.mjs → constants-B2QHb6u6.mjs} +4 -2
- package/dist/context/index.d.mts +11 -14
- package/dist/context/index.mjs +18 -38
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +8 -8
- package/dist/{core-5lazhva9.mjs → core-COcqo0Cp.mjs} +15 -5
- package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-zG13rpPJ.mjs} +97 -39
- package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-CWRz96Vr.mjs} +11 -8
- package/dist/{preloadResources-f3boenVZ.mjs → createApp-BGLYtErt.mjs} +305 -394
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-DSztofa9.d.mts} +1 -1
- package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation--3xguLkk.mjs} +1 -1
- package/dist/{defineResource-DxkMTjRn.mjs → defineResource-gUp9ug66.mjs} +118 -1024
- package/dist/discovery/index.d.mts +6 -7
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +11 -5
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-CkvG0LG2.mjs → elevation-BG4gnMsJ.mjs} +41 -12
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/encryption/index.d.mts +5 -5
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-COHw3N1b.mjs} +26 -5
- package/dist/{errors-BH7YU_9O.mjs → errors-CW4anJBy.mjs} +58 -23
- package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
- package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
- package/dist/{eventPlugin-zZY-unhr.mjs → eventPlugin-BweHAVws.mjs} +82 -21
- package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-N4-omQFR.mjs} +151 -17
- package/dist/events/index.d.mts +43 -18
- package/dist/events/index.mjs +49 -53
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +139 -157
- package/dist/events/transports/redis.d.mts +5 -6
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +42 -27
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DRlCIWE9.d.mts} +11 -25
- package/dist/{fields-BHrkoF5y.mjs → fields-A5crhsJb.mjs} +1 -1
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/filter-merge-DGM-GQ1J.mjs +99 -0
- package/dist/{generate-BLRf2m3y.mjs → generate-BXKo2Gc8.mjs} +2 -2
- package/dist/{headers-BIXWKrzQ.mjs → headers-DKZQKL-Z.mjs} +2 -2
- package/dist/health-BYAwA6YS.mjs +22 -0
- package/dist/{health-ZRHeDHXH.mjs → health-CrqUp3sz.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +36 -10
- package/dist/idempotency/index.mjs +230 -111
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +77 -12
- package/dist/{index-D4u76eQW.d.mts → index-Beg3aNnK.d.mts} +23 -101
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-BkV55WlV.d.mts} +37 -38
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/index-CUJvoyw3.d.mts +510 -0
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/{index-DHbyPo5j.d.mts → index-WH7xgvxS.d.mts} +304 -254
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +11 -11
- package/dist/{init-CpLkjU18.mjs → init-BPzK5Lnr.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +11 -7
- package/dist/integrations/event-gateway.mjs +4 -4
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +18 -5
- package/dist/integrations/mcp/index.d.mts +102 -89
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +8 -9
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +85 -144
- package/dist/integrations/streamline.mjs +131 -55
- package/dist/integrations/webhooks.d.mts +83 -143
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +5 -6
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +5 -6
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/integrations/websocket.mjs +2 -2
- package/dist/{interface-DEWNOMza.d.mts → interface-JJzwH4Ro.d.mts} +34 -2
- package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-YIq-vGLv.mjs} +2 -2
- package/dist/{keys-cpZCtcIE.mjs → keys-CpoiZ8hU.mjs} +7 -18
- package/dist/{loadResources-BPfWiI5s.mjs → loadResources-6jO52-RR.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-MP8xEppu.mjs} +2 -2
- package/dist/logger/index.d.mts +9 -17
- package/dist/logger/index.mjs +2 -153
- package/dist/logger-D4V1zGLI.mjs +160 -0
- package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-srPc7ItN.mjs} +446 -170
- package/dist/{memory-Iotz0cCx.mjs → memory-CTa5pOCW.mjs} +36 -34
- package/dist/{metrics-NMsmpRmY.mjs → metrics-DsZrvnpB.mjs} +39 -72
- package/dist/middleware/index.d.mts +5 -5
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.d.mts +13 -13
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-DpLJG2_N.mjs → module--L0iBIZz.mjs} +8 -8
- package/dist/mongodb-memory-server-CHhYSq5y.mjs +35539 -0
- package/dist/mongoose-CYJCLyi-.mjs +67451 -0
- package/dist/multiTenant-CAtYlW6H.d.mts +104 -0
- package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-Cc7eZ9cq.mjs} +46 -7
- package/dist/node-D8UZv_8c.mjs +41236 -0
- package/dist/{openapi-CmFf3yq_.mjs → openapi-CMegfsm8.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +5 -5
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Ryk270jd.mjs} +203 -33
- package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +8 -6
- package/dist/{permissions-D8TBH44E.mjs → permissions-C5ijIz5Q.mjs} +149 -33
- package/dist/{pipe-FMWNheSO.mjs → pipe-D81zMtWs.mjs} +2 -2
- package/dist/pipeline/index.d.mts +7 -7
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-CJGMKoui.mjs +565 -0
- package/dist/{BaseController-CYEwjyjN.mjs → plugin-DALd9dcF.mjs} +1269 -346
- package/dist/plugins/index.d.mts +17 -14
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.d.mts +5 -6
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-CXWZIzTv.mjs → pluralize-COUNYo3d.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +8 -8
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +35 -40
- package/dist/presets/index.mjs +4 -4
- package/dist/presets/multiTenant.d.mts +2 -107
- package/dist/presets/multiTenant.mjs +22 -162
- package/dist/presets/search.d.mts +9 -10
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-ZJSPgmT2.mjs → presets-CONf2Qem.mjs} +99 -107
- package/dist/{projection-BzIgzoKZ.mjs → projection-wFoLqdss.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-BATECpyh.mjs} +12 -14
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-DVEZ_UpV.mjs} +1 -1
- package/dist/{realtime-8tIQWZz4.mjs → realtime-xyubMKSB.mjs} +25 -21
- package/dist/{redis-Bzi1y3F9.d.mts → redis-BDgv9Glw.d.mts} +17 -24
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +15 -10
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-DNkgGQcK.mjs → registry-Bdzg-hdm.mjs} +13 -10
- package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-BFJxGPuh.mjs} +1 -1
- package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-Bnc6lJyy.mjs} +2 -2
- package/dist/{requestContext-cDkAUr-P.mjs → requestContext-C10UeFe5.mjs} +1 -1
- package/dist/requirement-Bu50QALH.mjs +35 -0
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-urWPJxrt.mjs +180 -0
- package/dist/rolldown-runtime-MYrLNGnq.mjs +45 -0
- package/dist/routeValidator-DHtpzrzk.mjs +70 -0
- package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-DIMKMV2G.mjs} +1 -1
- package/dist/{schedules-j6uXXAeJ.mjs → schedules-CKTs5ys8.mjs} +46 -37
- package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BVjkQ5DD.mjs} +2 -2
- package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-BA0Hb21Y.mjs} +2 -2
- package/dist/schemas/index.d.mts +20 -10
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.d.mts +13 -13
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +4 -4
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-BUi1xV1I.mjs → scope-DINaj7Ax.mjs} +4 -4
- package/dist/shutdown-LpLMH8T1.mjs +59 -0
- package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-DbwIM1f1.mjs} +1 -1
- package/dist/{sse-BeqVKQKu.mjs → sse-BzJFPX_1.mjs} +11 -15
- package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-DQChzGTs.mjs} +2 -2
- package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-Coj9RhOZ.mjs} +1 -1
- package/dist/{streaming-IcRFjWQU.mjs → streaming-JzB5CAga.mjs} +1 -1
- package/dist/subscriberIdentity-DeyppIif.mjs +28 -0
- package/dist/tenancy-DxV8OuX2.mjs +43 -0
- package/dist/tenantPreparation-CBvvAcAj.mjs +67 -0
- package/dist/testing/cleanupStoreContract.d.mts +4 -5
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +215 -197
- package/dist/testing/index.mjs +289 -214
- package/dist/testing/outboxStoreContract.d.mts +4 -5
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +16 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.d.mts +4 -5
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-CntavTlR.mjs +46 -0
- package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-orvnGu4F.mjs} +1 -1
- package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-kEaP_WmO.mjs} +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CWQn9-IC.d.mts → types-D3Ixsh00.d.mts} +22 -9
- package/dist/{types-CgenB1gv.mjs → types-fvO6Ovzx.mjs} +4 -4
- package/dist/usage/index.d.mts +5 -6
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-C76zD5pa.mjs} +1 -1
- package/dist/utils/index.d.mts +125 -264
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-CxcctrAh.mjs → utils-BpkVZ8rB.mjs} +315 -404
- package/dist/{validate-DrYWnOzI.mjs → validate-CWp-lsrm.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DKkT4hNJ.mjs → versioning-CHhL9P3o.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-CxKqoIFn.mjs → workScope-n9a7d_lZ.mjs} +2 -2
- package/package.json +57 -59
- package/skills/arc/SKILL.md +13 -4
- package/skills/arc/references/auth.md +8 -5
- package/skills/arc/references/integrations.md +1 -1
- package/skills/arc/references/mcp.md +8 -6
- package/skills/arc/references/multi-tenancy.md +19 -14
- package/skills/arc/references/production.md +27 -10
- package/dist/actionPermissions-BoPGQ5-4.mjs +0 -350
- package/dist/index-CFfLKMH4.d.mts +0 -290
- package/dist/interface-YzIbF7Ii.mjs +0 -17
- package/dist/redis-stream-BPKscsTB.d.mts +0 -260
- package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
- package/dist/rolldown-runtime-eysyzFAF.mjs +0 -15
- package/dist/sync/changelog.d.mts +0 -3
- package/dist/sync/changelog.mjs +0 -3
package/dist/testing/index.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { t as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { o as __toESM } from "../rolldown-runtime-MYrLNGnq.mjs";
|
|
3
|
+
import { t as CRUD_OPERATIONS } from "../constants-B2QHb6u6.mjs";
|
|
4
|
+
import { c as BaseController } from "../plugin-DALd9dcF.mjs";
|
|
5
|
+
import { g as getUserId } from "../types-fvO6Ovzx.mjs";
|
|
6
|
+
import { t as defineAggregation } from "../defineAggregation--3xguLkk.mjs";
|
|
7
|
+
import { t as defineResource } from "../defineResource-gUp9ug66.mjs";
|
|
8
|
+
import { E as requireOrgRole } from "../permissions-C5ijIz5Q.mjs";
|
|
9
|
+
import { h as asModulePorts, r as createApp } from "../createApp-BGLYtErt.mjs";
|
|
10
|
+
import { d as resolveModule, l as isLazyBridge, o as resolveModuleClosure } from "../plan-CJGMKoui.mjs";
|
|
11
|
+
import { n as getModuleExports } from "../module--L0iBIZz.mjs";
|
|
10
12
|
import { runStorageContract } from "./storageContract.mjs";
|
|
11
13
|
import Fastify from "fastify";
|
|
12
14
|
import { afterAll, describe, expect, it, vi } from "vitest";
|
|
@@ -134,6 +136,243 @@ function expectArc(response) {
|
|
|
134
136
|
return assertion;
|
|
135
137
|
}
|
|
136
138
|
//#endregion
|
|
139
|
+
//#region src/testing/testActor.ts
|
|
140
|
+
/**
|
|
141
|
+
* Test-only actor installation — how a package's HTTP tests say "run this
|
|
142
|
+
* request as a manager in org X".
|
|
143
|
+
*
|
|
144
|
+
* Identity in arc comes from `request.scope` and only from `request.scope`;
|
|
145
|
+
* headers like `x-role` are not identity. That rule left tests with no seam —
|
|
146
|
+
* `bootModuleApp` boots with `auth: false`, so every injected request arrived as
|
|
147
|
+
* `PUBLIC_SCOPE` and any role-gated route answered 401 with no supported way to
|
|
148
|
+
* be somebody.
|
|
149
|
+
*
|
|
150
|
+
* The `x-test-actor` header is a TRANSPORT for the test's intent, not a trusted
|
|
151
|
+
* claim. The authenticator that reads it is installed by `bootModuleApp` alone,
|
|
152
|
+
* behind the `@classytic/arc/testing` entrypoint; `createApp` never registers
|
|
153
|
+
* it, so no production build turns a forged header into identity. It is
|
|
154
|
+
* per-request rather than boot-only because the interesting authorization tests
|
|
155
|
+
* are the ones where a cashier is refused what a manager is allowed.
|
|
156
|
+
*
|
|
157
|
+
* Gate the routes under test with arc's own primitives — `requireRoles`,
|
|
158
|
+
* `requireOrgMembership`, `allOf(...)`. A test-specific gate would be a second
|
|
159
|
+
* implementation of authorization whose semantics could drift from the one
|
|
160
|
+
* actually shipping.
|
|
161
|
+
*/
|
|
162
|
+
/** The header the testing harness uses to carry a request's actor. */
|
|
163
|
+
const TEST_ACTOR_HEADER = "x-test-actor";
|
|
164
|
+
/**
|
|
165
|
+
* Build the headers that make an injected request act as `actor`.
|
|
166
|
+
*
|
|
167
|
+
* A string is shorthand for a single role. `null` means "stay public", so a test
|
|
168
|
+
* can prove an endpoint is reachable anonymously.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* t.app.inject({ method: 'GET', url: '/pos/shifts/current',
|
|
173
|
+
* headers: testActorHeaders('manager', ORG) });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
function testActorHeaders(actor, organizationId) {
|
|
177
|
+
if (actor === null) return {};
|
|
178
|
+
const org = organizationId !== void 0 ? { organizationId } : {};
|
|
179
|
+
const spec = typeof actor === "string" ? {
|
|
180
|
+
roles: [actor],
|
|
181
|
+
...org
|
|
182
|
+
} : {
|
|
183
|
+
...actor,
|
|
184
|
+
...org
|
|
185
|
+
};
|
|
186
|
+
return { [TEST_ACTOR_HEADER]: JSON.stringify(spec) };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Turn a spec into a `RequestScope`.
|
|
190
|
+
*
|
|
191
|
+
* `orgRoles` defaults to `roles`: a test that says "manager" means it at both
|
|
192
|
+
* levels, and making the caller repeat itself only produces tests that pass a
|
|
193
|
+
* global gate and mysteriously fail an org one.
|
|
194
|
+
*/
|
|
195
|
+
function scopeFromTestActor(spec) {
|
|
196
|
+
const roles = spec.roles ?? [];
|
|
197
|
+
const userId = spec.userId ?? `test-${roles.join("-") || "anon"}`;
|
|
198
|
+
if (spec.elevated !== void 0 && spec.elevated !== false) return {
|
|
199
|
+
kind: "elevated",
|
|
200
|
+
userId,
|
|
201
|
+
elevatedBy: spec.elevated === true ? "test" : spec.elevated,
|
|
202
|
+
...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {},
|
|
203
|
+
...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
|
|
204
|
+
};
|
|
205
|
+
if (spec.organizationId !== void 0) return {
|
|
206
|
+
kind: "member",
|
|
207
|
+
userId,
|
|
208
|
+
userRoles: roles,
|
|
209
|
+
organizationId: spec.organizationId,
|
|
210
|
+
orgRoles: spec.orgRoles ?? roles,
|
|
211
|
+
...spec.teamId !== void 0 ? { teamId: spec.teamId } : {},
|
|
212
|
+
...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
kind: "authenticated",
|
|
216
|
+
userId,
|
|
217
|
+
userRoles: roles
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Build the `auth` option that makes the test actor arc's authenticator.
|
|
222
|
+
*
|
|
223
|
+
* It must be an authenticator, not a hook. Fastify encapsulates plugins and
|
|
224
|
+
* modules register DURING `createApp`, so a hook added to the root instance
|
|
225
|
+
* afterwards never runs for their routes — every request still resolves as
|
|
226
|
+
* public and every role gate still answers 401, which reads exactly like a
|
|
227
|
+
* broken authorization rule in the code under test. Registering as
|
|
228
|
+
* `auth: { type: 'authenticator' }` puts the scope where arc's own auth would,
|
|
229
|
+
* at the phase arc expects, so tenant filtering and permission checks behave
|
|
230
|
+
* identically to production.
|
|
231
|
+
*
|
|
232
|
+
* `defaultActor` applies to requests carrying no header. A malformed header
|
|
233
|
+
* REJECTS rather than falling back to public — a test that meant to be an admin
|
|
234
|
+
* and got quietly demoted is the most expensive way to fail.
|
|
235
|
+
*/
|
|
236
|
+
function testActorAuth(defaultActor) {
|
|
237
|
+
const authenticate = async (request) => {
|
|
238
|
+
const req = request;
|
|
239
|
+
const raw = req.headers[TEST_ACTOR_HEADER];
|
|
240
|
+
let spec = defaultActor;
|
|
241
|
+
if (typeof raw === "string") try {
|
|
242
|
+
spec = JSON.parse(raw);
|
|
243
|
+
} catch {
|
|
244
|
+
throw new Error(`[arc-testing] malformed ${TEST_ACTOR_HEADER} header: ${raw}. Build it with testActorHeaders() rather than by hand.`);
|
|
245
|
+
}
|
|
246
|
+
if (spec === void 0) return;
|
|
247
|
+
req.scope = scopeFromTestActor(spec);
|
|
248
|
+
req.user = {
|
|
249
|
+
id: getUserId(req.scope) ?? "test-anon",
|
|
250
|
+
...spec.roles !== void 0 ? { role: spec.roles } : {},
|
|
251
|
+
...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {}
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
return {
|
|
255
|
+
type: "authenticator",
|
|
256
|
+
authenticate
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/testing/bootModuleApp.ts
|
|
261
|
+
/**
|
|
262
|
+
* The built-in DB factory: in-memory MongoDB via lazy devDep imports.
|
|
263
|
+
* Everything Mongo-specific about the harness lives HERE — the harness
|
|
264
|
+
* itself is driver-blind and any kit can replace this via `database`.
|
|
265
|
+
*/
|
|
266
|
+
async function mongoMemoryDatabase(opts) {
|
|
267
|
+
let mms;
|
|
268
|
+
try {
|
|
269
|
+
mms = await import("../mongodb-memory-server-CHhYSq5y.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
270
|
+
} catch {
|
|
271
|
+
throw new Error("[arc-testing] bootModuleApp's default database is in-memory MongoDB — install 'mongodb-memory-server' + 'mongoose' as devDependencies, or inject your own `database` factory for a different store");
|
|
272
|
+
}
|
|
273
|
+
const server = opts.replset ? await mms.MongoMemoryReplSet.create({ replSet: { count: 1 } }) : await mms.MongoMemoryServer.create();
|
|
274
|
+
const uri = server.getUri();
|
|
275
|
+
const mongoose = (await import("../mongoose-CYJCLyi-.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1))).default;
|
|
276
|
+
if (mongoose.connection.readyState !== 0) await mongoose.disconnect();
|
|
277
|
+
await mongoose.connect(uri);
|
|
278
|
+
mongoose.connection.deleteModel(/.*/);
|
|
279
|
+
return {
|
|
280
|
+
uri,
|
|
281
|
+
connection: mongoose.connection,
|
|
282
|
+
teardown: async () => {
|
|
283
|
+
if (mongoose.connection.readyState !== 0) await mongoose.disconnect().catch(() => void 0);
|
|
284
|
+
await server.stop().catch(() => void 0);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async function bootModuleApp(modules, options = {}) {
|
|
289
|
+
const db = await (options.database ?? mongoMemoryDatabase)({ replset: options.replset ?? false });
|
|
290
|
+
const ctx = {
|
|
291
|
+
uri: db.uri,
|
|
292
|
+
connection: db.connection
|
|
293
|
+
};
|
|
294
|
+
const resolved = typeof modules === "function" ? await modules(ctx) : modules;
|
|
295
|
+
const list = Array.isArray(resolved) ? resolved : [resolved];
|
|
296
|
+
let app;
|
|
297
|
+
try {
|
|
298
|
+
app = await createApp({
|
|
299
|
+
preset: "testing",
|
|
300
|
+
/**
|
|
301
|
+
* The test actor is arc's AUTHENTICATOR rather than a bolted-on hook, so the scope
|
|
302
|
+
* lands at the phase the rest of the pipeline expects — tenant filtering and
|
|
303
|
+
* permission checks then behave exactly as they do in production.
|
|
304
|
+
*
|
|
305
|
+
* This replaces the old `auth: false`. The authenticator is inert for a request
|
|
306
|
+
* carrying no `x-test-actor` header and no configured default, so a suite that
|
|
307
|
+
* never mentions an actor behaves as it did before. `options.app` still wins, so a
|
|
308
|
+
* suite exercising real auth can override it.
|
|
309
|
+
*/
|
|
310
|
+
auth: testActorAuth(options.actor),
|
|
311
|
+
sensible: false,
|
|
312
|
+
...options.app,
|
|
313
|
+
...options.ports ? { ports: options.ports } : {},
|
|
314
|
+
modules: list
|
|
315
|
+
});
|
|
316
|
+
await app.ready();
|
|
317
|
+
} catch (err) {
|
|
318
|
+
await db.teardown().catch(() => void 0);
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
let closed = false;
|
|
322
|
+
return {
|
|
323
|
+
app,
|
|
324
|
+
uri: db.uri,
|
|
325
|
+
connection: db.connection,
|
|
326
|
+
exports: (name) => getModuleExports(app, name),
|
|
327
|
+
close: async () => {
|
|
328
|
+
if (closed) return;
|
|
329
|
+
closed = true;
|
|
330
|
+
await app.close().catch(() => void 0);
|
|
331
|
+
await db.teardown().catch(() => void 0);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/testing/assertModuleBootsAlone.ts
|
|
337
|
+
/**
|
|
338
|
+
* Boot a module inside its closure — `resolveModuleClosure` (its `dependsOn` and
|
|
339
|
+
* `requires.modules`, transitively, plus every platform module) and every bridge in the list,
|
|
340
|
+
* which arc activates only when both ends are present — and fail naming the module when the boot
|
|
341
|
+
* or the `probe` throws. A hidden edge fails here, by name.
|
|
342
|
+
*
|
|
343
|
+
* ```ts
|
|
344
|
+
* it("order boots alone", () => assertModuleBootsAlone(allModules, "order", { probe: (t) => drivePorts(t) }));
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
async function assertModuleBootsAlone(modules, name, options = {}) {
|
|
348
|
+
const roots = typeof name === "string" ? [name] : name;
|
|
349
|
+
const label = roots.map((r) => `"${r}"`).join(" + ");
|
|
350
|
+
let closure = [];
|
|
351
|
+
try {
|
|
352
|
+
const { probe, ...bootOptions } = options;
|
|
353
|
+
const t = await bootModuleApp(async (ctx) => {
|
|
354
|
+
const resolved = typeof modules === "function" ? await modules(ctx) : modules;
|
|
355
|
+
const inputs = isInputList(resolved) ? resolved : [resolved];
|
|
356
|
+
const nodes = await Promise.all(inputs.map((input) => isLazyBridge(input) ? input : resolveModule(input)));
|
|
357
|
+
const included = new Set(resolveModuleClosure(nodes, roots).map((m) => m.name));
|
|
358
|
+
closure = nodes.filter((m) => included.has(m.name)).map((m) => m.name);
|
|
359
|
+
return nodes.filter((m) => included.has(m.name) || m.kind === "bridge");
|
|
360
|
+
}, bootOptions);
|
|
361
|
+
try {
|
|
362
|
+
await probe?.(t);
|
|
363
|
+
} finally {
|
|
364
|
+
await t.close();
|
|
365
|
+
}
|
|
366
|
+
} catch (err) {
|
|
367
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
368
|
+
throw new Error(`[arc/testing] module ${label} does not boot alone in its closure [${closure.join(", ") || "unresolved"}]: ${msg}`, { cause: err });
|
|
369
|
+
}
|
|
370
|
+
return { closure };
|
|
371
|
+
}
|
|
372
|
+
function isInputList(value) {
|
|
373
|
+
return Array.isArray(value);
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
137
376
|
//#region src/testing/mocks.ts
|
|
138
377
|
/**
|
|
139
378
|
* Create a mock repository for testing
|
|
@@ -833,202 +1072,6 @@ async function setupBetterAuthTestApp(input) {
|
|
|
833
1072
|
};
|
|
834
1073
|
}
|
|
835
1074
|
//#endregion
|
|
836
|
-
//#region src/testing/testActor.ts
|
|
837
|
-
/**
|
|
838
|
-
* Test-only actor installation — how a package's HTTP tests say "run this
|
|
839
|
-
* request as a manager in org X".
|
|
840
|
-
*
|
|
841
|
-
* Identity in arc comes from `request.scope` and only from `request.scope`;
|
|
842
|
-
* headers like `x-role` are not identity. That rule left tests with no seam —
|
|
843
|
-
* `bootModuleApp` boots with `auth: false`, so every injected request arrived as
|
|
844
|
-
* `PUBLIC_SCOPE` and any role-gated route answered 401 with no supported way to
|
|
845
|
-
* be somebody.
|
|
846
|
-
*
|
|
847
|
-
* The `x-test-actor` header is a TRANSPORT for the test's intent, not a trusted
|
|
848
|
-
* claim. The authenticator that reads it is installed by `bootModuleApp` alone,
|
|
849
|
-
* behind the `@classytic/arc/testing` entrypoint; `createApp` never registers
|
|
850
|
-
* it, so no production build turns a forged header into identity. It is
|
|
851
|
-
* per-request rather than boot-only because the interesting authorization tests
|
|
852
|
-
* are the ones where a cashier is refused what a manager is allowed.
|
|
853
|
-
*
|
|
854
|
-
* Gate the routes under test with arc's own primitives — `requireRoles`,
|
|
855
|
-
* `requireOrgMembership`, `allOf(...)`. A test-specific gate would be a second
|
|
856
|
-
* implementation of authorization whose semantics could drift from the one
|
|
857
|
-
* actually shipping.
|
|
858
|
-
*/
|
|
859
|
-
/** The header the testing harness uses to carry a request's actor. */
|
|
860
|
-
const TEST_ACTOR_HEADER = "x-test-actor";
|
|
861
|
-
/**
|
|
862
|
-
* Build the headers that make an injected request act as `actor`.
|
|
863
|
-
*
|
|
864
|
-
* A string is shorthand for a single role. `null` means "stay public", so a test
|
|
865
|
-
* can prove an endpoint is reachable anonymously.
|
|
866
|
-
*
|
|
867
|
-
* @example
|
|
868
|
-
* ```ts
|
|
869
|
-
* t.app.inject({ method: 'GET', url: '/pos/shifts/current',
|
|
870
|
-
* headers: testActorHeaders('manager', ORG) });
|
|
871
|
-
* ```
|
|
872
|
-
*/
|
|
873
|
-
function testActorHeaders(actor, organizationId) {
|
|
874
|
-
if (actor === null) return {};
|
|
875
|
-
const org = organizationId !== void 0 ? { organizationId } : {};
|
|
876
|
-
const spec = typeof actor === "string" ? {
|
|
877
|
-
roles: [actor],
|
|
878
|
-
...org
|
|
879
|
-
} : {
|
|
880
|
-
...actor,
|
|
881
|
-
...org
|
|
882
|
-
};
|
|
883
|
-
return { [TEST_ACTOR_HEADER]: JSON.stringify(spec) };
|
|
884
|
-
}
|
|
885
|
-
/**
|
|
886
|
-
* Turn a spec into a `RequestScope`.
|
|
887
|
-
*
|
|
888
|
-
* `orgRoles` defaults to `roles`: a test that says "manager" means it at both
|
|
889
|
-
* levels, and making the caller repeat itself only produces tests that pass a
|
|
890
|
-
* global gate and mysteriously fail an org one.
|
|
891
|
-
*/
|
|
892
|
-
function scopeFromTestActor(spec) {
|
|
893
|
-
const roles = spec.roles ?? [];
|
|
894
|
-
const userId = spec.userId ?? `test-${roles.join("-") || "anon"}`;
|
|
895
|
-
if (spec.elevated !== void 0 && spec.elevated !== false) return {
|
|
896
|
-
kind: "elevated",
|
|
897
|
-
userId,
|
|
898
|
-
elevatedBy: spec.elevated === true ? "test" : spec.elevated,
|
|
899
|
-
...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {},
|
|
900
|
-
...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
|
|
901
|
-
};
|
|
902
|
-
if (spec.organizationId !== void 0) return {
|
|
903
|
-
kind: "member",
|
|
904
|
-
userId,
|
|
905
|
-
userRoles: roles,
|
|
906
|
-
organizationId: spec.organizationId,
|
|
907
|
-
orgRoles: spec.orgRoles ?? roles,
|
|
908
|
-
...spec.teamId !== void 0 ? { teamId: spec.teamId } : {},
|
|
909
|
-
...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
|
|
910
|
-
};
|
|
911
|
-
return {
|
|
912
|
-
kind: "authenticated",
|
|
913
|
-
userId,
|
|
914
|
-
userRoles: roles
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
/**
|
|
918
|
-
* Build the `auth` option that makes the test actor arc's authenticator.
|
|
919
|
-
*
|
|
920
|
-
* It must be an authenticator, not a hook. Fastify encapsulates plugins and
|
|
921
|
-
* modules register DURING `createApp`, so a hook added to the root instance
|
|
922
|
-
* afterwards never runs for their routes — every request still resolves as
|
|
923
|
-
* public and every role gate still answers 401, which reads exactly like a
|
|
924
|
-
* broken authorization rule in the code under test. Registering as
|
|
925
|
-
* `auth: { type: 'authenticator' }` puts the scope where arc's own auth would,
|
|
926
|
-
* at the phase arc expects, so tenant filtering and permission checks behave
|
|
927
|
-
* identically to production.
|
|
928
|
-
*
|
|
929
|
-
* `defaultActor` applies to requests carrying no header. A malformed header
|
|
930
|
-
* REJECTS rather than falling back to public — a test that meant to be an admin
|
|
931
|
-
* and got quietly demoted is the most expensive way to fail.
|
|
932
|
-
*/
|
|
933
|
-
function testActorAuth(defaultActor) {
|
|
934
|
-
const authenticate = async (request) => {
|
|
935
|
-
const req = request;
|
|
936
|
-
const raw = req.headers[TEST_ACTOR_HEADER];
|
|
937
|
-
let spec = defaultActor;
|
|
938
|
-
if (typeof raw === "string") try {
|
|
939
|
-
spec = JSON.parse(raw);
|
|
940
|
-
} catch {
|
|
941
|
-
throw new Error(`[arc-testing] malformed ${TEST_ACTOR_HEADER} header: ${raw}. Build it with testActorHeaders() rather than by hand.`);
|
|
942
|
-
}
|
|
943
|
-
if (spec === void 0) return;
|
|
944
|
-
req.scope = scopeFromTestActor(spec);
|
|
945
|
-
req.user = {
|
|
946
|
-
id: getUserId(req.scope) ?? "test-anon",
|
|
947
|
-
...spec.roles !== void 0 ? { role: spec.roles } : {},
|
|
948
|
-
...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {}
|
|
949
|
-
};
|
|
950
|
-
};
|
|
951
|
-
return {
|
|
952
|
-
type: "authenticator",
|
|
953
|
-
authenticate
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
//#endregion
|
|
957
|
-
//#region src/testing/bootModuleApp.ts
|
|
958
|
-
/**
|
|
959
|
-
* The built-in DB factory: in-memory MongoDB via lazy devDep imports.
|
|
960
|
-
* Everything Mongo-specific about the harness lives HERE — the harness
|
|
961
|
-
* itself is driver-blind and any kit can replace this via `database`.
|
|
962
|
-
*/
|
|
963
|
-
async function mongoMemoryDatabase(opts) {
|
|
964
|
-
let mms;
|
|
965
|
-
try {
|
|
966
|
-
mms = await import("mongodb-memory-server");
|
|
967
|
-
} catch {
|
|
968
|
-
throw new Error("[arc-testing] bootModuleApp's default database is in-memory MongoDB — install 'mongodb-memory-server' + 'mongoose' as devDependencies, or inject your own `database` factory for a different store");
|
|
969
|
-
}
|
|
970
|
-
const server = opts.replset ? await mms.MongoMemoryReplSet.create({ replSet: { count: 1 } }) : await mms.MongoMemoryServer.create();
|
|
971
|
-
const uri = server.getUri();
|
|
972
|
-
const mongoose = (await import("mongoose")).default;
|
|
973
|
-
if (mongoose.connection.readyState !== 0) await mongoose.disconnect();
|
|
974
|
-
await mongoose.connect(uri);
|
|
975
|
-
mongoose.connection.deleteModel(/.*/);
|
|
976
|
-
return {
|
|
977
|
-
uri,
|
|
978
|
-
connection: mongoose.connection,
|
|
979
|
-
teardown: async () => {
|
|
980
|
-
if (mongoose.connection.readyState !== 0) await mongoose.disconnect().catch(() => void 0);
|
|
981
|
-
await server.stop().catch(() => void 0);
|
|
982
|
-
}
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
async function bootModuleApp(modules, options = {}) {
|
|
986
|
-
const db = await (options.database ?? mongoMemoryDatabase)({ replset: options.replset ?? false });
|
|
987
|
-
const ctx = {
|
|
988
|
-
uri: db.uri,
|
|
989
|
-
connection: db.connection
|
|
990
|
-
};
|
|
991
|
-
const resolved = typeof modules === "function" ? await modules(ctx) : modules;
|
|
992
|
-
const list = Array.isArray(resolved) ? resolved : [resolved];
|
|
993
|
-
let app;
|
|
994
|
-
try {
|
|
995
|
-
app = await createApp({
|
|
996
|
-
preset: "testing",
|
|
997
|
-
/**
|
|
998
|
-
* The test actor is arc's AUTHENTICATOR rather than a bolted-on hook, so the scope
|
|
999
|
-
* lands at the phase the rest of the pipeline expects — tenant filtering and
|
|
1000
|
-
* permission checks then behave exactly as they do in production.
|
|
1001
|
-
*
|
|
1002
|
-
* This replaces the old `auth: false`. The authenticator is inert for a request
|
|
1003
|
-
* carrying no `x-test-actor` header and no configured default, so a suite that
|
|
1004
|
-
* never mentions an actor behaves as it did before. `options.app` still wins, so a
|
|
1005
|
-
* suite exercising real auth can override it.
|
|
1006
|
-
*/
|
|
1007
|
-
auth: testActorAuth(options.actor),
|
|
1008
|
-
sensible: false,
|
|
1009
|
-
...options.app,
|
|
1010
|
-
modules: list
|
|
1011
|
-
});
|
|
1012
|
-
await app.ready();
|
|
1013
|
-
} catch (err) {
|
|
1014
|
-
await db.teardown().catch(() => void 0);
|
|
1015
|
-
throw err;
|
|
1016
|
-
}
|
|
1017
|
-
let closed = false;
|
|
1018
|
-
return {
|
|
1019
|
-
app,
|
|
1020
|
-
uri: db.uri,
|
|
1021
|
-
connection: db.connection,
|
|
1022
|
-
exports: (name) => getModuleExports(app, name),
|
|
1023
|
-
close: async () => {
|
|
1024
|
-
if (closed) return;
|
|
1025
|
-
closed = true;
|
|
1026
|
-
await app.close().catch(() => void 0);
|
|
1027
|
-
await db.teardown().catch(() => void 0);
|
|
1028
|
-
}
|
|
1029
|
-
};
|
|
1030
|
-
}
|
|
1031
|
-
//#endregion
|
|
1032
1075
|
//#region src/testing/fixtures.ts
|
|
1033
1076
|
function createTestFixtures() {
|
|
1034
1077
|
const registry = /* @__PURE__ */ new Map();
|
|
@@ -1356,13 +1399,45 @@ function createHttpTestHarness(resource, optionsOrGetter) {
|
|
|
1356
1399
|
}
|
|
1357
1400
|
//#endregion
|
|
1358
1401
|
//#region src/testing/moduleSetup.ts
|
|
1359
|
-
|
|
1402
|
+
/**
|
|
1403
|
+
* A real `ModuleSetupContext` for tests that invoke a module's setup phases
|
|
1404
|
+
* DIRECTLY — `mod.bootstrap(app, ctx)`, `mod.plugins(app, ctx)` — instead of
|
|
1405
|
+
* booting a whole app through `createApp` / `bootModuleApp`.
|
|
1406
|
+
*
|
|
1407
|
+
* ## Why this is not `{ defer: () => {} }`
|
|
1408
|
+
*
|
|
1409
|
+
* That literal satisfies the type and throws away every disposer the module
|
|
1410
|
+
* registers. A test written against it passes whether the module releases what
|
|
1411
|
+
* it acquired or leaks it, so the one thing `defer` exists to make testable
|
|
1412
|
+
* becomes the one thing that cannot be tested. It also gives a false negative
|
|
1413
|
+
* in the other direction: a module that starts deferring later keeps its green
|
|
1414
|
+
* test, which is precisely the "compiles today, wrong tomorrow" shape the
|
|
1415
|
+
* disposer contract was introduced to remove.
|
|
1416
|
+
*
|
|
1417
|
+
* This collects them instead, and `dispose()` unwinds LIFO — the same order
|
|
1418
|
+
* arc's own teardown uses, so an ordering bug surfaces here rather than in
|
|
1419
|
+
* production shutdown.
|
|
1420
|
+
*
|
|
1421
|
+
* @example
|
|
1422
|
+
* ```ts
|
|
1423
|
+
* const setup = createTestModuleSetup();
|
|
1424
|
+
* const engine = await mod.bootstrap?.(app, setup.context);
|
|
1425
|
+
*
|
|
1426
|
+
* expect(setup.deferred).toHaveLength(1); // it registered its teardown
|
|
1427
|
+
* await setup.dispose(); // and it actually releases
|
|
1428
|
+
* expect(engine.closed).toBe(true);
|
|
1429
|
+
* ```
|
|
1430
|
+
*/
|
|
1431
|
+
function createTestModuleSetup(options = {}) {
|
|
1360
1432
|
const deferred = [];
|
|
1361
1433
|
return {
|
|
1362
|
-
context: {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1434
|
+
context: {
|
|
1435
|
+
ports: asModulePorts(Object.freeze({ ...options.ports })),
|
|
1436
|
+
defer(disposer) {
|
|
1437
|
+
if (typeof disposer !== "function") throw new TypeError(`[arc/testing] defer() expected a function, received ${typeof disposer}.`);
|
|
1438
|
+
deferred.push(disposer);
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1366
1441
|
deferred,
|
|
1367
1442
|
async dispose() {
|
|
1368
1443
|
const errors = [];
|
|
@@ -1473,7 +1548,7 @@ function scopeRoleGate(...allowed) {
|
|
|
1473
1548
|
*/
|
|
1474
1549
|
async function startInMemoryMongo() {
|
|
1475
1550
|
try {
|
|
1476
|
-
const { MongoMemoryServer } = await import("mongodb-memory-server");
|
|
1551
|
+
const { MongoMemoryServer } = await import("../mongodb-memory-server-CHhYSq5y.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
1477
1552
|
const mongod = await MongoMemoryServer.create();
|
|
1478
1553
|
return {
|
|
1479
1554
|
uri: mongod.getUri(),
|
|
@@ -1487,7 +1562,7 @@ async function startInMemoryMongo() {
|
|
|
1487
1562
|
}
|
|
1488
1563
|
async function connectMongooseToUri(uri) {
|
|
1489
1564
|
try {
|
|
1490
|
-
const mongoose = (await import("mongoose")).default;
|
|
1565
|
+
const mongoose = (await import("../mongoose-CYJCLyi-.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1))).default;
|
|
1491
1566
|
await mongoose.connect(uri);
|
|
1492
1567
|
return { async disconnect() {
|
|
1493
1568
|
await mongoose.disconnect();
|
|
@@ -1504,7 +1579,7 @@ function pickDefaultAuth(authMode, callerAuth) {
|
|
|
1504
1579
|
};
|
|
1505
1580
|
}
|
|
1506
1581
|
async function createTestApp(options = {}) {
|
|
1507
|
-
const { createApp } = await import("../
|
|
1582
|
+
const { createApp } = await import("../createApp-BGLYtErt.mjs").then((n) => n.i);
|
|
1508
1583
|
const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
|
|
1509
1584
|
let dbHandle;
|
|
1510
1585
|
let dbUri;
|
|
@@ -1574,4 +1649,4 @@ function createMinimalTestApp(options = {}) {
|
|
|
1574
1649
|
});
|
|
1575
1650
|
}
|
|
1576
1651
|
//#endregion
|
|
1577
|
-
export { HttpTestHarness, TEST_ACTOR_HEADER, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestModuleSetup, createTestTimer, expectArc, mongoMemoryDatabase,
|
|
1652
|
+
export { HttpTestHarness, TEST_ACTOR_HEADER, assertModuleBootsAlone, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestModuleSetup, createTestTimer, expectArc, mongoMemoryDatabase, runAuthorizationConformance, runStorageContract, safeParseBody, scopeFromTestActor, scopeRoleGate, setupBetterAuthTestApp, testActorAuth, testActorHeaders, waitFor };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
3
3
|
//#region src/testing/outboxStoreContract.d.ts
|
|
4
|
-
interface OutboxStoreContractSetupResult {
|
|
4
|
+
export interface OutboxStoreContractSetupResult {
|
|
5
5
|
store: OutboxStore;
|
|
6
6
|
/** Called once after the suite. */
|
|
7
7
|
teardown?: () => Promise<void>;
|
|
@@ -17,7 +17,6 @@ interface OutboxStoreContractSetupResult {
|
|
|
17
17
|
*/
|
|
18
18
|
reset: () => Promise<void>;
|
|
19
19
|
}
|
|
20
|
-
type OutboxStoreContractSetup = () => Promise<OutboxStoreContractSetupResult>;
|
|
21
|
-
declare function runOutboxStoreContract(name: string, setup: OutboxStoreContractSetup): void;
|
|
22
|
-
//#endregion
|
|
23
|
-
export { OutboxStoreContractSetup, OutboxStoreContractSetupResult, runOutboxStoreContract };
|
|
20
|
+
export type OutboxStoreContractSetup = () => Promise<OutboxStoreContractSetupResult>;
|
|
21
|
+
export declare function runOutboxStoreContract(name: string, setup: OutboxStoreContractSetup): void;
|
|
22
|
+
//#endregion
|