@mandujs/core 0.53.2 → 0.54.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/README.md +654 -654
- package/package.json +2 -2
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -380
- package/src/diagnose/checks.ts +832 -720
- package/src/diagnose/index.ts +17 -16
- package/src/diagnose/run.ts +93 -91
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +24 -31
- package/src/runtime/streaming-ssr.ts +32 -37
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
package/src/testing/session.ts
CHANGED
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @mandujs/core/testing/session
|
|
3
|
-
*
|
|
4
|
-
* Pre-baked login state for integration tests.
|
|
5
|
-
*
|
|
6
|
-
* Most test scenarios start with "given a logged-in user, when...". This
|
|
7
|
-
* module returns a ready-to-use set of request headers (specifically,
|
|
8
|
-
* a `Cookie` header bearing a signed session payload) without going
|
|
9
|
-
* through the live `/login` flow — which would couple the test to the
|
|
10
|
-
* app's password, CSRF, and rate-limit policies.
|
|
11
|
-
*
|
|
12
|
-
* ```ts
|
|
13
|
-
* import { createTestSession } from "@mandujs/core/testing";
|
|
14
|
-
*
|
|
15
|
-
* const authed = await createTestSession({ userId: "u_42", roles: ["admin"] });
|
|
16
|
-
*
|
|
17
|
-
* const res = await server.fetch("/dashboard", {
|
|
18
|
-
* headers: authed.headers,
|
|
19
|
-
* });
|
|
20
|
-
* expect(res.status).toBe(200);
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* ## Contract
|
|
24
|
-
*
|
|
25
|
-
* - **Storage compatibility**: the cookie produced here is consumable by the
|
|
26
|
-
* same `createCookieSessionStorage()` the app uses in production, as long
|
|
27
|
-
* as the shared `secret` matches. Tests typically pass the app's real
|
|
28
|
-
* storage instance via `{ storage }`; otherwise the fixture spins up a
|
|
29
|
-
* fresh one with a deterministic test secret.
|
|
30
|
-
* - **No network**: everything happens in-process — no HTTP roundtrip, no
|
|
31
|
-
* CSRF token generation, no rate-limit counters incremented.
|
|
32
|
-
* - **Fully typed**: `userId` is required, `extras` carries arbitrary
|
|
33
|
-
* JSON-serializable payload. Cookie attributes (name, path, secure) come
|
|
34
|
-
* from the provided storage's options.
|
|
35
|
-
*
|
|
36
|
-
* @module testing/session
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import {
|
|
40
|
-
Session,
|
|
41
|
-
createCookieSessionStorage,
|
|
42
|
-
type SessionStorage,
|
|
43
|
-
type CookieSessionOptions,
|
|
44
|
-
} from "../filling/session";
|
|
45
|
-
import { CookieManager } from "../filling/context";
|
|
46
|
-
|
|
47
|
-
/** Key under which login helpers persist the user id. Mirrors `auth/login.ts`. */
|
|
48
|
-
const USER_ID_KEY = "userId";
|
|
49
|
-
/** Key under which login helpers persist the login timestamp. Mirrors `auth/login.ts`. */
|
|
50
|
-
const LOGGED_AT_KEY = "loginAt";
|
|
51
|
-
|
|
52
|
-
/** Default session secret used when the caller does not pass a storage instance. */
|
|
53
|
-
const DEFAULT_TEST_SECRET = "mandu-test-secret-do-not-use-in-production";
|
|
54
|
-
|
|
55
|
-
/** Options for {@link createTestSession}. */
|
|
56
|
-
export interface CreateTestSessionOptions {
|
|
57
|
-
/** User id persisted in the session. Required. */
|
|
58
|
-
userId: string;
|
|
59
|
-
/** Arbitrary additional session data (JSON-serializable). */
|
|
60
|
-
extras?: Record<string, unknown>;
|
|
61
|
-
/**
|
|
62
|
-
* Login timestamp to persist. Default: `Date.now()` at call time.
|
|
63
|
-
* Useful for age-sensitive flows (e.g., re-auth prompts).
|
|
64
|
-
*/
|
|
65
|
-
loggedAt?: number;
|
|
66
|
-
/**
|
|
67
|
-
* Storage instance to commit against. When omitted, the fixture spins up
|
|
68
|
-
* a fresh in-memory cookie-backed storage with {@link DEFAULT_TEST_SECRET}.
|
|
69
|
-
* Pass your app's real storage (same `secret`) to make the cookie
|
|
70
|
-
* round-trippable by the server under test.
|
|
71
|
-
*/
|
|
72
|
-
storage?: SessionStorage;
|
|
73
|
-
/**
|
|
74
|
-
* Overrides for the fixture-created storage. Ignored when `storage` is
|
|
75
|
-
* passed directly.
|
|
76
|
-
*/
|
|
77
|
-
cookieOptions?: Partial<CookieSessionOptions["cookie"]>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** The return shape. */
|
|
81
|
-
export interface TestSession {
|
|
82
|
-
/** Raw `Set-Cookie` header string produced by committing the session. */
|
|
83
|
-
readonly setCookie: string;
|
|
84
|
-
/** `Cookie: ...` value suitable for outbound requests. */
|
|
85
|
-
readonly cookieHeader: string;
|
|
86
|
-
/** Headers object with `Cookie` pre-set — spread into `fetch()` directly. */
|
|
87
|
-
readonly headers: Record<string, string>;
|
|
88
|
-
/** The storage instance used to commit — re-use for subsequent setup steps. */
|
|
89
|
-
readonly storage: SessionStorage;
|
|
90
|
-
/** The `Session` instance that was committed (read-only snapshot view). */
|
|
91
|
-
readonly session: Session;
|
|
92
|
-
/** Convenience: the `userId` the fixture was created for. */
|
|
93
|
-
readonly userId: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Build a ready-to-use authenticated session.
|
|
98
|
-
*
|
|
99
|
-
* **Why not just POST `/login`?** Because the login endpoint can be behind
|
|
100
|
-
* CSRF, rate limit, captcha, 2FA, etc. The testing need is to assert
|
|
101
|
-
* behaviour *given* a logged-in user — not to re-validate the login path.
|
|
102
|
-
* For login-path tests, call the route directly with `server.fetch("/login", …)`.
|
|
103
|
-
*/
|
|
104
|
-
export async function createTestSession(
|
|
105
|
-
options: CreateTestSessionOptions,
|
|
106
|
-
): Promise<TestSession> {
|
|
107
|
-
const { userId, extras, loggedAt, cookieOptions } = options;
|
|
108
|
-
|
|
109
|
-
if (typeof userId !== "string" || userId.length === 0) {
|
|
110
|
-
throw new TypeError(
|
|
111
|
-
"[testing/session] createTestSession: 'userId' must be a non-empty string.",
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const storage =
|
|
116
|
-
options.storage ??
|
|
117
|
-
createCookieSessionStorage({
|
|
118
|
-
cookie: {
|
|
119
|
-
name: cookieOptions?.name ?? "__session",
|
|
120
|
-
secrets: cookieOptions?.secrets ?? [DEFAULT_TEST_SECRET],
|
|
121
|
-
httpOnly: cookieOptions?.httpOnly ?? true,
|
|
122
|
-
// Tests run over plain HTTP — forcing Secure would make the cookie
|
|
123
|
-
// invisible to the server under test. Explicit `false` overrides the
|
|
124
|
-
// production-env default in `createCookieSessionStorage`.
|
|
125
|
-
secure: cookieOptions?.secure ?? false,
|
|
126
|
-
sameSite: cookieOptions?.sameSite ?? "lax",
|
|
127
|
-
maxAge: cookieOptions?.maxAge ?? 86_400,
|
|
128
|
-
path: cookieOptions?.path ?? "/",
|
|
129
|
-
domain: cookieOptions?.domain,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const session = new Session();
|
|
134
|
-
session.set(USER_ID_KEY, userId);
|
|
135
|
-
session.set(LOGGED_AT_KEY, typeof loggedAt === "number" ? loggedAt : Date.now());
|
|
136
|
-
|
|
137
|
-
if (extras) {
|
|
138
|
-
for (const [key, value] of Object.entries(extras)) {
|
|
139
|
-
session.set(key, value);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const setCookie = await storage.commitSession(session);
|
|
144
|
-
const cookieHeader = extractCookieValuePair(setCookie);
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
setCookie,
|
|
148
|
-
cookieHeader,
|
|
149
|
-
headers: { Cookie: cookieHeader },
|
|
150
|
-
storage,
|
|
151
|
-
session,
|
|
152
|
-
userId,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Given a Set-Cookie string, return the `name=value` segment suitable for
|
|
158
|
-
* an outgoing `Cookie` request header. Attribute fields (Path, Max-Age,
|
|
159
|
-
* HttpOnly, SameSite, ...) are stripped.
|
|
160
|
-
*
|
|
161
|
-
* The value MUST NOT be URL-decoded — the server-side cookie parser expects
|
|
162
|
-
* the same encoding that was produced by `commitSession`.
|
|
163
|
-
*
|
|
164
|
-
* Exported so tests that build their own Set-Cookie (custom storage) can
|
|
165
|
-
* share the parsing logic.
|
|
166
|
-
*/
|
|
167
|
-
export function extractCookieValuePair(setCookie: string): string {
|
|
168
|
-
const end = setCookie.indexOf(";");
|
|
169
|
-
return end === -1 ? setCookie : setCookie.slice(0, end);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Read the session that `storage.getSession()` would yield for the given
|
|
174
|
-
* `Cookie:` header. Useful in tests that install their own middleware and
|
|
175
|
-
* want to assert what the handler would see.
|
|
176
|
-
*
|
|
177
|
-
* Thin wrapper that constructs a throwaway `Request` (the form the
|
|
178
|
-
* production `CookieManager` expects) and delegates to `getSession` —
|
|
179
|
-
* so tests never have to thread the plumbing themselves.
|
|
180
|
-
*/
|
|
181
|
-
export async function readSession(
|
|
182
|
-
storage: SessionStorage,
|
|
183
|
-
cookieHeader: string,
|
|
184
|
-
): Promise<Session> {
|
|
185
|
-
const request = new Request("http://localhost/__testing/readSession", {
|
|
186
|
-
headers: { cookie: cookieHeader },
|
|
187
|
-
});
|
|
188
|
-
const cookies = new CookieManager(request);
|
|
189
|
-
return storage.getSession(cookies);
|
|
190
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @mandujs/core/testing/session
|
|
3
|
+
*
|
|
4
|
+
* Pre-baked login state for integration tests.
|
|
5
|
+
*
|
|
6
|
+
* Most test scenarios start with "given a logged-in user, when...". This
|
|
7
|
+
* module returns a ready-to-use set of request headers (specifically,
|
|
8
|
+
* a `Cookie` header bearing a signed session payload) without going
|
|
9
|
+
* through the live `/login` flow — which would couple the test to the
|
|
10
|
+
* app's password, CSRF, and rate-limit policies.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { createTestSession } from "@mandujs/core/testing";
|
|
14
|
+
*
|
|
15
|
+
* const authed = await createTestSession({ userId: "u_42", roles: ["admin"] });
|
|
16
|
+
*
|
|
17
|
+
* const res = await server.fetch("/dashboard", {
|
|
18
|
+
* headers: authed.headers,
|
|
19
|
+
* });
|
|
20
|
+
* expect(res.status).toBe(200);
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ## Contract
|
|
24
|
+
*
|
|
25
|
+
* - **Storage compatibility**: the cookie produced here is consumable by the
|
|
26
|
+
* same `createCookieSessionStorage()` the app uses in production, as long
|
|
27
|
+
* as the shared `secret` matches. Tests typically pass the app's real
|
|
28
|
+
* storage instance via `{ storage }`; otherwise the fixture spins up a
|
|
29
|
+
* fresh one with a deterministic test secret.
|
|
30
|
+
* - **No network**: everything happens in-process — no HTTP roundtrip, no
|
|
31
|
+
* CSRF token generation, no rate-limit counters incremented.
|
|
32
|
+
* - **Fully typed**: `userId` is required, `extras` carries arbitrary
|
|
33
|
+
* JSON-serializable payload. Cookie attributes (name, path, secure) come
|
|
34
|
+
* from the provided storage's options.
|
|
35
|
+
*
|
|
36
|
+
* @module testing/session
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
Session,
|
|
41
|
+
createCookieSessionStorage,
|
|
42
|
+
type SessionStorage,
|
|
43
|
+
type CookieSessionOptions,
|
|
44
|
+
} from "../filling/session";
|
|
45
|
+
import { CookieManager } from "../filling/context";
|
|
46
|
+
|
|
47
|
+
/** Key under which login helpers persist the user id. Mirrors `auth/login.ts`. */
|
|
48
|
+
const USER_ID_KEY = "userId";
|
|
49
|
+
/** Key under which login helpers persist the login timestamp. Mirrors `auth/login.ts`. */
|
|
50
|
+
const LOGGED_AT_KEY = "loginAt";
|
|
51
|
+
|
|
52
|
+
/** Default session secret used when the caller does not pass a storage instance. */
|
|
53
|
+
const DEFAULT_TEST_SECRET = "mandu-test-secret-do-not-use-in-production";
|
|
54
|
+
|
|
55
|
+
/** Options for {@link createTestSession}. */
|
|
56
|
+
export interface CreateTestSessionOptions {
|
|
57
|
+
/** User id persisted in the session. Required. */
|
|
58
|
+
userId: string;
|
|
59
|
+
/** Arbitrary additional session data (JSON-serializable). */
|
|
60
|
+
extras?: Record<string, unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Login timestamp to persist. Default: `Date.now()` at call time.
|
|
63
|
+
* Useful for age-sensitive flows (e.g., re-auth prompts).
|
|
64
|
+
*/
|
|
65
|
+
loggedAt?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Storage instance to commit against. When omitted, the fixture spins up
|
|
68
|
+
* a fresh in-memory cookie-backed storage with {@link DEFAULT_TEST_SECRET}.
|
|
69
|
+
* Pass your app's real storage (same `secret`) to make the cookie
|
|
70
|
+
* round-trippable by the server under test.
|
|
71
|
+
*/
|
|
72
|
+
storage?: SessionStorage;
|
|
73
|
+
/**
|
|
74
|
+
* Overrides for the fixture-created storage. Ignored when `storage` is
|
|
75
|
+
* passed directly.
|
|
76
|
+
*/
|
|
77
|
+
cookieOptions?: Partial<CookieSessionOptions["cookie"]>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The return shape. */
|
|
81
|
+
export interface TestSession {
|
|
82
|
+
/** Raw `Set-Cookie` header string produced by committing the session. */
|
|
83
|
+
readonly setCookie: string;
|
|
84
|
+
/** `Cookie: ...` value suitable for outbound requests. */
|
|
85
|
+
readonly cookieHeader: string;
|
|
86
|
+
/** Headers object with `Cookie` pre-set — spread into `fetch()` directly. */
|
|
87
|
+
readonly headers: Record<string, string>;
|
|
88
|
+
/** The storage instance used to commit — re-use for subsequent setup steps. */
|
|
89
|
+
readonly storage: SessionStorage;
|
|
90
|
+
/** The `Session` instance that was committed (read-only snapshot view). */
|
|
91
|
+
readonly session: Session;
|
|
92
|
+
/** Convenience: the `userId` the fixture was created for. */
|
|
93
|
+
readonly userId: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build a ready-to-use authenticated session.
|
|
98
|
+
*
|
|
99
|
+
* **Why not just POST `/login`?** Because the login endpoint can be behind
|
|
100
|
+
* CSRF, rate limit, captcha, 2FA, etc. The testing need is to assert
|
|
101
|
+
* behaviour *given* a logged-in user — not to re-validate the login path.
|
|
102
|
+
* For login-path tests, call the route directly with `server.fetch("/login", …)`.
|
|
103
|
+
*/
|
|
104
|
+
export async function createTestSession(
|
|
105
|
+
options: CreateTestSessionOptions,
|
|
106
|
+
): Promise<TestSession> {
|
|
107
|
+
const { userId, extras, loggedAt, cookieOptions } = options;
|
|
108
|
+
|
|
109
|
+
if (typeof userId !== "string" || userId.length === 0) {
|
|
110
|
+
throw new TypeError(
|
|
111
|
+
"[testing/session] createTestSession: 'userId' must be a non-empty string.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const storage =
|
|
116
|
+
options.storage ??
|
|
117
|
+
createCookieSessionStorage({
|
|
118
|
+
cookie: {
|
|
119
|
+
name: cookieOptions?.name ?? "__session",
|
|
120
|
+
secrets: cookieOptions?.secrets ?? [DEFAULT_TEST_SECRET],
|
|
121
|
+
httpOnly: cookieOptions?.httpOnly ?? true,
|
|
122
|
+
// Tests run over plain HTTP — forcing Secure would make the cookie
|
|
123
|
+
// invisible to the server under test. Explicit `false` overrides the
|
|
124
|
+
// production-env default in `createCookieSessionStorage`.
|
|
125
|
+
secure: cookieOptions?.secure ?? false,
|
|
126
|
+
sameSite: cookieOptions?.sameSite ?? "lax",
|
|
127
|
+
maxAge: cookieOptions?.maxAge ?? 86_400,
|
|
128
|
+
path: cookieOptions?.path ?? "/",
|
|
129
|
+
domain: cookieOptions?.domain,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const session = new Session();
|
|
134
|
+
session.set(USER_ID_KEY, userId);
|
|
135
|
+
session.set(LOGGED_AT_KEY, typeof loggedAt === "number" ? loggedAt : Date.now());
|
|
136
|
+
|
|
137
|
+
if (extras) {
|
|
138
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
139
|
+
session.set(key, value);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const setCookie = await storage.commitSession(session);
|
|
144
|
+
const cookieHeader = extractCookieValuePair(setCookie);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
setCookie,
|
|
148
|
+
cookieHeader,
|
|
149
|
+
headers: { Cookie: cookieHeader },
|
|
150
|
+
storage,
|
|
151
|
+
session,
|
|
152
|
+
userId,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Given a Set-Cookie string, return the `name=value` segment suitable for
|
|
158
|
+
* an outgoing `Cookie` request header. Attribute fields (Path, Max-Age,
|
|
159
|
+
* HttpOnly, SameSite, ...) are stripped.
|
|
160
|
+
*
|
|
161
|
+
* The value MUST NOT be URL-decoded — the server-side cookie parser expects
|
|
162
|
+
* the same encoding that was produced by `commitSession`.
|
|
163
|
+
*
|
|
164
|
+
* Exported so tests that build their own Set-Cookie (custom storage) can
|
|
165
|
+
* share the parsing logic.
|
|
166
|
+
*/
|
|
167
|
+
export function extractCookieValuePair(setCookie: string): string {
|
|
168
|
+
const end = setCookie.indexOf(";");
|
|
169
|
+
return end === -1 ? setCookie : setCookie.slice(0, end);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Read the session that `storage.getSession()` would yield for the given
|
|
174
|
+
* `Cookie:` header. Useful in tests that install their own middleware and
|
|
175
|
+
* want to assert what the handler would see.
|
|
176
|
+
*
|
|
177
|
+
* Thin wrapper that constructs a throwaway `Request` (the form the
|
|
178
|
+
* production `CookieManager` expects) and delegates to `getSession` —
|
|
179
|
+
* so tests never have to thread the plumbing themselves.
|
|
180
|
+
*/
|
|
181
|
+
export async function readSession(
|
|
182
|
+
storage: SessionStorage,
|
|
183
|
+
cookieHeader: string,
|
|
184
|
+
): Promise<Session> {
|
|
185
|
+
const request = new Request("http://localhost/__testing/readSession", {
|
|
186
|
+
headers: { cookie: cookieHeader },
|
|
187
|
+
});
|
|
188
|
+
const cookies = new CookieManager(request);
|
|
189
|
+
return storage.getSession(cookies);
|
|
190
|
+
}
|
package/src/types/branded.ts
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Branded Types - Nominal typing for string identifiers
|
|
3
|
-
*
|
|
4
|
-
* Prevents accidental mixing of different ID types at compile time.
|
|
5
|
-
* All branded types are structurally compatible with plain strings at runtime.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* const col = collectionId("blog");
|
|
10
|
-
* const entry = entryId("hello-world");
|
|
11
|
-
* // col and entry are both strings at runtime,
|
|
12
|
-
* // but TypeScript treats them as distinct types.
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
declare const __brand: unique symbol;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Brand utility type - creates nominal types from structural types.
|
|
20
|
-
* The brand exists only at the type level and has zero runtime cost.
|
|
21
|
-
*/
|
|
22
|
-
export type Brand<T, B extends string> = T & { readonly [__brand]: B };
|
|
23
|
-
|
|
24
|
-
/** Branded string identifying a content collection */
|
|
25
|
-
export type CollectionId = Brand<string, "CollectionId">;
|
|
26
|
-
|
|
27
|
-
/** Branded string identifying a content entry within a collection */
|
|
28
|
-
export type EntryId = Brand<string, "EntryId">;
|
|
29
|
-
|
|
30
|
-
/** Branded string containing sanitized HTML (XSS-safe) */
|
|
31
|
-
export type SafeHTML = Brand<string, "SafeHTML">;
|
|
32
|
-
|
|
33
|
-
/** Branded string identifying a route in the manifest */
|
|
34
|
-
export type RouteId = Brand<string, "RouteId">;
|
|
35
|
-
|
|
36
|
-
// -- Constructor functions --
|
|
37
|
-
|
|
38
|
-
/** Create a branded CollectionId from a plain string */
|
|
39
|
-
export function collectionId(id: string): CollectionId {
|
|
40
|
-
return id as CollectionId;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Create a branded EntryId from a plain string */
|
|
44
|
-
export function entryId(id: string): EntryId {
|
|
45
|
-
return id as EntryId;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Create a branded SafeHTML from a sanitized string */
|
|
49
|
-
export function safeHTML(html: string): SafeHTML {
|
|
50
|
-
return html as SafeHTML;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Create a branded RouteId from a plain string */
|
|
54
|
-
export function routeId(id: string): RouteId {
|
|
55
|
-
return id as RouteId;
|
|
56
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Branded Types - Nominal typing for string identifiers
|
|
3
|
+
*
|
|
4
|
+
* Prevents accidental mixing of different ID types at compile time.
|
|
5
|
+
* All branded types are structurally compatible with plain strings at runtime.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const col = collectionId("blog");
|
|
10
|
+
* const entry = entryId("hello-world");
|
|
11
|
+
* // col and entry are both strings at runtime,
|
|
12
|
+
* // but TypeScript treats them as distinct types.
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
declare const __brand: unique symbol;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Brand utility type - creates nominal types from structural types.
|
|
20
|
+
* The brand exists only at the type level and has zero runtime cost.
|
|
21
|
+
*/
|
|
22
|
+
export type Brand<T, B extends string> = T & { readonly [__brand]: B };
|
|
23
|
+
|
|
24
|
+
/** Branded string identifying a content collection */
|
|
25
|
+
export type CollectionId = Brand<string, "CollectionId">;
|
|
26
|
+
|
|
27
|
+
/** Branded string identifying a content entry within a collection */
|
|
28
|
+
export type EntryId = Brand<string, "EntryId">;
|
|
29
|
+
|
|
30
|
+
/** Branded string containing sanitized HTML (XSS-safe) */
|
|
31
|
+
export type SafeHTML = Brand<string, "SafeHTML">;
|
|
32
|
+
|
|
33
|
+
/** Branded string identifying a route in the manifest */
|
|
34
|
+
export type RouteId = Brand<string, "RouteId">;
|
|
35
|
+
|
|
36
|
+
// -- Constructor functions --
|
|
37
|
+
|
|
38
|
+
/** Create a branded CollectionId from a plain string */
|
|
39
|
+
export function collectionId(id: string): CollectionId {
|
|
40
|
+
return id as CollectionId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Create a branded EntryId from a plain string */
|
|
44
|
+
export function entryId(id: string): EntryId {
|
|
45
|
+
return id as EntryId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Create a branded SafeHTML from a sanitized string */
|
|
49
|
+
export function safeHTML(html: string): SafeHTML {
|
|
50
|
+
return html as SafeHTML;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Create a branded RouteId from a plain string */
|
|
54
|
+
export function routeId(id: string): RouteId {
|
|
55
|
+
return id as RouteId;
|
|
56
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./branded";
|
|
1
|
+
export * from "./branded";
|