@mandujs/core 0.53.3 → 0.54.1
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 +1 -1
- 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 -451
- package/src/diagnose/checks.ts +832 -832
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +93 -93
- 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/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
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the module-level HTTP error helpers (Phase 6.3).
|
|
3
|
-
*
|
|
4
|
-
* Companion to the ctx.unauthorized() / ctx.forbidden() / ctx.error()
|
|
5
|
-
* methods on ManduContext — those live in filling/context.ts. These
|
|
6
|
-
* standalone helpers must match ctx behavior for body shape while also
|
|
7
|
-
* covering the header-merging contract (WWW-Authenticate, custom headers).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect } from "bun:test";
|
|
11
|
-
import { unauthorized, forbidden, badRequest } from "../http-errors";
|
|
12
|
-
|
|
13
|
-
async function readJson(res: Response): Promise<unknown> {
|
|
14
|
-
const clone = res.clone();
|
|
15
|
-
const text = await clone.text();
|
|
16
|
-
return text.length > 0 ? JSON.parse(text) : null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe("unauthorized()", () => {
|
|
20
|
-
it("returns 401 with the default WWW-Authenticate: Bearer header", async () => {
|
|
21
|
-
const res = unauthorized();
|
|
22
|
-
expect(res.status).toBe(401);
|
|
23
|
-
expect(res.headers.get("WWW-Authenticate")).toBe("Bearer");
|
|
24
|
-
expect(await readJson(res)).toEqual({ error: "Unauthorized" });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("includes the caller-provided message in the JSON body", async () => {
|
|
28
|
-
const res = unauthorized("login required");
|
|
29
|
-
expect(res.status).toBe(401);
|
|
30
|
-
expect(await readJson(res)).toEqual({ error: "login required" });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("merges custom headers alongside WWW-Authenticate", async () => {
|
|
34
|
-
const res = unauthorized(undefined, { headers: { "x-custom": "1" } });
|
|
35
|
-
expect(res.status).toBe(401);
|
|
36
|
-
expect(res.headers.get("x-custom")).toBe("1");
|
|
37
|
-
expect(res.headers.get("WWW-Authenticate")).toBe("Bearer");
|
|
38
|
-
expect(res.headers.get("Content-Type")).toContain("application/json");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("lets callers override WWW-Authenticate (e.g. Basic realm)", () => {
|
|
42
|
-
const res = unauthorized("Token expired", {
|
|
43
|
-
headers: { "WWW-Authenticate": 'Basic realm="app"' },
|
|
44
|
-
});
|
|
45
|
-
expect(res.headers.get("WWW-Authenticate")).toBe('Basic realm="app"');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe("forbidden()", () => {
|
|
50
|
-
it("returns 403 with the default JSON body", async () => {
|
|
51
|
-
const res = forbidden();
|
|
52
|
-
expect(res.status).toBe(403);
|
|
53
|
-
expect(await readJson(res)).toEqual({ error: "Forbidden" });
|
|
54
|
-
expect(res.headers.get("Content-Type")).toContain("application/json");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("carries the provided message", async () => {
|
|
58
|
-
const res = forbidden("role mismatch");
|
|
59
|
-
expect(res.status).toBe(403);
|
|
60
|
-
expect(await readJson(res)).toEqual({ error: "role mismatch" });
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("merges custom response headers", () => {
|
|
64
|
-
const res = forbidden("no", { headers: { "x-trace": "abc" } });
|
|
65
|
-
expect(res.headers.get("x-trace")).toBe("abc");
|
|
66
|
-
expect(res.status).toBe(403);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe("badRequest()", () => {
|
|
71
|
-
it("string input produces { error: <string> }", async () => {
|
|
72
|
-
const res = badRequest("string-only");
|
|
73
|
-
expect(res.status).toBe(400);
|
|
74
|
-
expect(await readJson(res)).toEqual({ error: "string-only" });
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("object input with message + errors produces { error, errors }", async () => {
|
|
78
|
-
const res = badRequest({
|
|
79
|
-
message: "invalid",
|
|
80
|
-
errors: { email: ["required"] },
|
|
81
|
-
});
|
|
82
|
-
expect(res.status).toBe(400);
|
|
83
|
-
expect(await readJson(res)).toEqual({
|
|
84
|
-
error: "invalid",
|
|
85
|
-
errors: { email: ["required"] },
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("object input without errors omits the key (no `errors: undefined` leak)", async () => {
|
|
90
|
-
const res = badRequest({ message: "simple" });
|
|
91
|
-
const body = (await readJson(res)) as Record<string, unknown>;
|
|
92
|
-
expect(body).toEqual({ error: "simple" });
|
|
93
|
-
// Explicit: `errors` key must not appear at all — users pattern-match on presence.
|
|
94
|
-
expect(Object.prototype.hasOwnProperty.call(body, "errors")).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("no-args call defaults to 'Bad Request'", async () => {
|
|
98
|
-
const res = badRequest();
|
|
99
|
-
expect(res.status).toBe(400);
|
|
100
|
-
expect(await readJson(res)).toEqual({ error: "Bad Request" });
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("merges custom headers on both string and object inputs", () => {
|
|
104
|
-
const resA = badRequest("x", { headers: { "x-a": "1" } });
|
|
105
|
-
expect(resA.headers.get("x-a")).toBe("1");
|
|
106
|
-
|
|
107
|
-
const resB = badRequest({ message: "y" }, { headers: { "x-b": "2" } });
|
|
108
|
-
expect(resB.headers.get("x-b")).toBe("2");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("response content-type is application/json; charset=utf-8", () => {
|
|
112
|
-
const res = badRequest("z");
|
|
113
|
-
const ct = res.headers.get("Content-Type") ?? "";
|
|
114
|
-
expect(ct).toContain("application/json");
|
|
115
|
-
expect(ct).toContain("charset=utf-8");
|
|
116
|
-
});
|
|
117
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the module-level HTTP error helpers (Phase 6.3).
|
|
3
|
+
*
|
|
4
|
+
* Companion to the ctx.unauthorized() / ctx.forbidden() / ctx.error()
|
|
5
|
+
* methods on ManduContext — those live in filling/context.ts. These
|
|
6
|
+
* standalone helpers must match ctx behavior for body shape while also
|
|
7
|
+
* covering the header-merging contract (WWW-Authenticate, custom headers).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from "bun:test";
|
|
11
|
+
import { unauthorized, forbidden, badRequest } from "../http-errors";
|
|
12
|
+
|
|
13
|
+
async function readJson(res: Response): Promise<unknown> {
|
|
14
|
+
const clone = res.clone();
|
|
15
|
+
const text = await clone.text();
|
|
16
|
+
return text.length > 0 ? JSON.parse(text) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("unauthorized()", () => {
|
|
20
|
+
it("returns 401 with the default WWW-Authenticate: Bearer header", async () => {
|
|
21
|
+
const res = unauthorized();
|
|
22
|
+
expect(res.status).toBe(401);
|
|
23
|
+
expect(res.headers.get("WWW-Authenticate")).toBe("Bearer");
|
|
24
|
+
expect(await readJson(res)).toEqual({ error: "Unauthorized" });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("includes the caller-provided message in the JSON body", async () => {
|
|
28
|
+
const res = unauthorized("login required");
|
|
29
|
+
expect(res.status).toBe(401);
|
|
30
|
+
expect(await readJson(res)).toEqual({ error: "login required" });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("merges custom headers alongside WWW-Authenticate", async () => {
|
|
34
|
+
const res = unauthorized(undefined, { headers: { "x-custom": "1" } });
|
|
35
|
+
expect(res.status).toBe(401);
|
|
36
|
+
expect(res.headers.get("x-custom")).toBe("1");
|
|
37
|
+
expect(res.headers.get("WWW-Authenticate")).toBe("Bearer");
|
|
38
|
+
expect(res.headers.get("Content-Type")).toContain("application/json");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("lets callers override WWW-Authenticate (e.g. Basic realm)", () => {
|
|
42
|
+
const res = unauthorized("Token expired", {
|
|
43
|
+
headers: { "WWW-Authenticate": 'Basic realm="app"' },
|
|
44
|
+
});
|
|
45
|
+
expect(res.headers.get("WWW-Authenticate")).toBe('Basic realm="app"');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("forbidden()", () => {
|
|
50
|
+
it("returns 403 with the default JSON body", async () => {
|
|
51
|
+
const res = forbidden();
|
|
52
|
+
expect(res.status).toBe(403);
|
|
53
|
+
expect(await readJson(res)).toEqual({ error: "Forbidden" });
|
|
54
|
+
expect(res.headers.get("Content-Type")).toContain("application/json");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("carries the provided message", async () => {
|
|
58
|
+
const res = forbidden("role mismatch");
|
|
59
|
+
expect(res.status).toBe(403);
|
|
60
|
+
expect(await readJson(res)).toEqual({ error: "role mismatch" });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("merges custom response headers", () => {
|
|
64
|
+
const res = forbidden("no", { headers: { "x-trace": "abc" } });
|
|
65
|
+
expect(res.headers.get("x-trace")).toBe("abc");
|
|
66
|
+
expect(res.status).toBe(403);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("badRequest()", () => {
|
|
71
|
+
it("string input produces { error: <string> }", async () => {
|
|
72
|
+
const res = badRequest("string-only");
|
|
73
|
+
expect(res.status).toBe(400);
|
|
74
|
+
expect(await readJson(res)).toEqual({ error: "string-only" });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("object input with message + errors produces { error, errors }", async () => {
|
|
78
|
+
const res = badRequest({
|
|
79
|
+
message: "invalid",
|
|
80
|
+
errors: { email: ["required"] },
|
|
81
|
+
});
|
|
82
|
+
expect(res.status).toBe(400);
|
|
83
|
+
expect(await readJson(res)).toEqual({
|
|
84
|
+
error: "invalid",
|
|
85
|
+
errors: { email: ["required"] },
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("object input without errors omits the key (no `errors: undefined` leak)", async () => {
|
|
90
|
+
const res = badRequest({ message: "simple" });
|
|
91
|
+
const body = (await readJson(res)) as Record<string, unknown>;
|
|
92
|
+
expect(body).toEqual({ error: "simple" });
|
|
93
|
+
// Explicit: `errors` key must not appear at all — users pattern-match on presence.
|
|
94
|
+
expect(Object.prototype.hasOwnProperty.call(body, "errors")).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("no-args call defaults to 'Bad Request'", async () => {
|
|
98
|
+
const res = badRequest();
|
|
99
|
+
expect(res.status).toBe(400);
|
|
100
|
+
expect(await readJson(res)).toEqual({ error: "Bad Request" });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("merges custom headers on both string and object inputs", () => {
|
|
104
|
+
const resA = badRequest("x", { headers: { "x-a": "1" } });
|
|
105
|
+
expect(resA.headers.get("x-a")).toBe("1");
|
|
106
|
+
|
|
107
|
+
const resB = badRequest({ message: "y" }, { headers: { "x-b": "2" } });
|
|
108
|
+
expect(resB.headers.get("x-b")).toBe("2");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("response content-type is application/json; charset=utf-8", () => {
|
|
112
|
+
const res = badRequest("z");
|
|
113
|
+
const ct = res.headers.get("Content-Type") ?? "";
|
|
114
|
+
expect(ct).toContain("application/json");
|
|
115
|
+
expect(ct).toContain("charset=utf-8");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for `notFound()` / `isNotFoundResponse()` (Phase 6.3).
|
|
3
|
-
*
|
|
4
|
-
* Mirrors the shape of the redirect() tests (see packages/core/tests/server/
|
|
5
|
-
* redirect-loader.test.ts). These stay local to the helper — integration
|
|
6
|
-
* coverage that exercises the full SSR pipeline lives in the server test
|
|
7
|
-
* at packages/core/tests/server/not-found-page.test.ts.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect } from "bun:test";
|
|
11
|
-
import {
|
|
12
|
-
notFound,
|
|
13
|
-
isNotFoundResponse,
|
|
14
|
-
NOT_FOUND_BRAND,
|
|
15
|
-
} from "../not-found";
|
|
16
|
-
import { redirect } from "../redirect";
|
|
17
|
-
import { ManduFilling } from "../../filling/filling";
|
|
18
|
-
import { ManduContext } from "../../filling/context";
|
|
19
|
-
|
|
20
|
-
describe("notFound()", () => {
|
|
21
|
-
it("returns a Response with status 404", () => {
|
|
22
|
-
const res = notFound();
|
|
23
|
-
expect(res).toBeInstanceOf(Response);
|
|
24
|
-
expect(res.status).toBe(404);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("carries the provided message in its body", async () => {
|
|
28
|
-
const res = notFound({ message: "Post not found" });
|
|
29
|
-
const body = await res.text();
|
|
30
|
-
expect(body).toBe("Post not found");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("is recognized by isNotFoundResponse()", () => {
|
|
34
|
-
expect(isNotFoundResponse(notFound())).toBe(true);
|
|
35
|
-
expect(isNotFoundResponse(notFound({ message: "x" }))).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("rejects a bare `new Response(null, { status: 404 })` (brand check)", () => {
|
|
39
|
-
const bare = new Response(null, { status: 404 });
|
|
40
|
-
expect(isNotFoundResponse(bare)).toBe(false);
|
|
41
|
-
// Double-check: something we know is 404 but not ours (e.g. upstream proxy)
|
|
42
|
-
const proxied = new Response("upstream 404", {
|
|
43
|
-
status: 404,
|
|
44
|
-
headers: { "Content-Type": "text/plain" },
|
|
45
|
-
});
|
|
46
|
-
expect(isNotFoundResponse(proxied)).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("rejects a redirect() Response (notFound is a distinct brand)", () => {
|
|
50
|
-
expect(isNotFoundResponse(redirect("/foo"))).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("rejects null / undefined / {} / primitives (type guard is strict)", () => {
|
|
54
|
-
expect(isNotFoundResponse(null)).toBe(false);
|
|
55
|
-
expect(isNotFoundResponse(undefined)).toBe(false);
|
|
56
|
-
expect(isNotFoundResponse({})).toBe(false);
|
|
57
|
-
expect(isNotFoundResponse("string")).toBe(false);
|
|
58
|
-
expect(isNotFoundResponse(404)).toBe(false);
|
|
59
|
-
expect(isNotFoundResponse(new Error("not a response"))).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("status is always 404 regardless of the message", () => {
|
|
63
|
-
expect(notFound().status).toBe(404);
|
|
64
|
-
expect(notFound({ message: "a" }).status).toBe(404);
|
|
65
|
-
expect(notFound({ message: "" }).status).toBe(404); // empty still 404
|
|
66
|
-
expect(notFound({ message: "x".repeat(1000) }).status).toBe(404);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("body is text/plain (not JSON) — explicit content-type contract", async () => {
|
|
70
|
-
const res = notFound({ message: "missing" });
|
|
71
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
72
|
-
expect(ct).toContain("text/plain");
|
|
73
|
-
expect(ct).toContain("charset=utf-8");
|
|
74
|
-
// Body must be the literal message, not a JSON envelope.
|
|
75
|
-
expect(await res.text()).toBe("missing");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("default body (no args) is the literal 'Not Found'", async () => {
|
|
79
|
-
const res = notFound();
|
|
80
|
-
expect(await res.text()).toBe("Not Found");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("loader returning notFound() is picked up by isNotFoundResponse", async () => {
|
|
84
|
-
const filling = new ManduFilling();
|
|
85
|
-
filling.loader(() => notFound({ message: "post gone" }));
|
|
86
|
-
|
|
87
|
-
const ctx = new ManduContext(new Request("http://test/post/1"), { id: "1" });
|
|
88
|
-
const returned = await filling.executeLoader(ctx);
|
|
89
|
-
expect(isNotFoundResponse(returned)).toBe(true);
|
|
90
|
-
// And the body survived.
|
|
91
|
-
const body = await (returned as Response).text();
|
|
92
|
-
expect(body).toBe("post gone");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("loader throwing notFound() is picked up the same way (Remix idiom)", async () => {
|
|
96
|
-
const filling = new ManduFilling();
|
|
97
|
-
filling.loader(() => {
|
|
98
|
-
throw notFound({ message: "thrown" });
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const ctx = new ManduContext(new Request("http://test/x"), {});
|
|
102
|
-
let caught: unknown = null;
|
|
103
|
-
try {
|
|
104
|
-
await filling.executeLoader(ctx);
|
|
105
|
-
} catch (e) {
|
|
106
|
-
caught = e;
|
|
107
|
-
}
|
|
108
|
-
expect(isNotFoundResponse(caught)).toBe(true);
|
|
109
|
-
expect(await (caught as Response).text()).toBe("thrown");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("integration simulation: loadPageData-like branch distinguishes return vs throw", async () => {
|
|
113
|
-
// Emulates the exact shape of the SSR pipeline's try/catch: `returned`
|
|
114
|
-
// and `thrown` both route through `isNotFoundResponse`. This is the
|
|
115
|
-
// contract the server integration relies on.
|
|
116
|
-
const returnCase = notFound({ message: "A" });
|
|
117
|
-
const throwCase = notFound({ message: "B" });
|
|
118
|
-
|
|
119
|
-
// Both must be recognised.
|
|
120
|
-
expect(isNotFoundResponse(returnCase)).toBe(true);
|
|
121
|
-
expect(isNotFoundResponse(throwCase)).toBe(true);
|
|
122
|
-
|
|
123
|
-
// And each preserves its own message (no shared mutable state).
|
|
124
|
-
expect(await returnCase.text()).toBe("A");
|
|
125
|
-
expect(await throwCase.text()).toBe("B");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("accepts a call with no arguments and still produces a valid sentinel", () => {
|
|
129
|
-
const res = notFound();
|
|
130
|
-
expect(res).toBeInstanceOf(Response);
|
|
131
|
-
expect(res.status).toBe(404);
|
|
132
|
-
expect(isNotFoundResponse(res)).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("exports NOT_FOUND_BRAND as a unique symbol keyed to the package", () => {
|
|
136
|
-
// The brand must be a Symbol.for() so multiple copies of @mandujs/core
|
|
137
|
-
// (e.g. monorepo duplication) agree on identity. If this changes we've
|
|
138
|
-
// broken cross-package detection — regression guard.
|
|
139
|
-
expect(typeof NOT_FOUND_BRAND).toBe("symbol");
|
|
140
|
-
expect(Symbol.keyFor(NOT_FOUND_BRAND)).toBe("@mandujs/core/not-found");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("two separate notFound() calls produce independently-branded Responses", () => {
|
|
144
|
-
// WeakSet membership is per-instance — the brand on one must not
|
|
145
|
-
// leak to another. Important for test isolation.
|
|
146
|
-
const a = notFound();
|
|
147
|
-
const b = notFound();
|
|
148
|
-
expect(a).not.toBe(b);
|
|
149
|
-
expect(isNotFoundResponse(a)).toBe(true);
|
|
150
|
-
expect(isNotFoundResponse(b)).toBe(true);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `notFound()` / `isNotFoundResponse()` (Phase 6.3).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the shape of the redirect() tests (see packages/core/tests/server/
|
|
5
|
+
* redirect-loader.test.ts). These stay local to the helper — integration
|
|
6
|
+
* coverage that exercises the full SSR pipeline lives in the server test
|
|
7
|
+
* at packages/core/tests/server/not-found-page.test.ts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from "bun:test";
|
|
11
|
+
import {
|
|
12
|
+
notFound,
|
|
13
|
+
isNotFoundResponse,
|
|
14
|
+
NOT_FOUND_BRAND,
|
|
15
|
+
} from "../not-found";
|
|
16
|
+
import { redirect } from "../redirect";
|
|
17
|
+
import { ManduFilling } from "../../filling/filling";
|
|
18
|
+
import { ManduContext } from "../../filling/context";
|
|
19
|
+
|
|
20
|
+
describe("notFound()", () => {
|
|
21
|
+
it("returns a Response with status 404", () => {
|
|
22
|
+
const res = notFound();
|
|
23
|
+
expect(res).toBeInstanceOf(Response);
|
|
24
|
+
expect(res.status).toBe(404);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("carries the provided message in its body", async () => {
|
|
28
|
+
const res = notFound({ message: "Post not found" });
|
|
29
|
+
const body = await res.text();
|
|
30
|
+
expect(body).toBe("Post not found");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is recognized by isNotFoundResponse()", () => {
|
|
34
|
+
expect(isNotFoundResponse(notFound())).toBe(true);
|
|
35
|
+
expect(isNotFoundResponse(notFound({ message: "x" }))).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("rejects a bare `new Response(null, { status: 404 })` (brand check)", () => {
|
|
39
|
+
const bare = new Response(null, { status: 404 });
|
|
40
|
+
expect(isNotFoundResponse(bare)).toBe(false);
|
|
41
|
+
// Double-check: something we know is 404 but not ours (e.g. upstream proxy)
|
|
42
|
+
const proxied = new Response("upstream 404", {
|
|
43
|
+
status: 404,
|
|
44
|
+
headers: { "Content-Type": "text/plain" },
|
|
45
|
+
});
|
|
46
|
+
expect(isNotFoundResponse(proxied)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("rejects a redirect() Response (notFound is a distinct brand)", () => {
|
|
50
|
+
expect(isNotFoundResponse(redirect("/foo"))).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("rejects null / undefined / {} / primitives (type guard is strict)", () => {
|
|
54
|
+
expect(isNotFoundResponse(null)).toBe(false);
|
|
55
|
+
expect(isNotFoundResponse(undefined)).toBe(false);
|
|
56
|
+
expect(isNotFoundResponse({})).toBe(false);
|
|
57
|
+
expect(isNotFoundResponse("string")).toBe(false);
|
|
58
|
+
expect(isNotFoundResponse(404)).toBe(false);
|
|
59
|
+
expect(isNotFoundResponse(new Error("not a response"))).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("status is always 404 regardless of the message", () => {
|
|
63
|
+
expect(notFound().status).toBe(404);
|
|
64
|
+
expect(notFound({ message: "a" }).status).toBe(404);
|
|
65
|
+
expect(notFound({ message: "" }).status).toBe(404); // empty still 404
|
|
66
|
+
expect(notFound({ message: "x".repeat(1000) }).status).toBe(404);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("body is text/plain (not JSON) — explicit content-type contract", async () => {
|
|
70
|
+
const res = notFound({ message: "missing" });
|
|
71
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
72
|
+
expect(ct).toContain("text/plain");
|
|
73
|
+
expect(ct).toContain("charset=utf-8");
|
|
74
|
+
// Body must be the literal message, not a JSON envelope.
|
|
75
|
+
expect(await res.text()).toBe("missing");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("default body (no args) is the literal 'Not Found'", async () => {
|
|
79
|
+
const res = notFound();
|
|
80
|
+
expect(await res.text()).toBe("Not Found");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("loader returning notFound() is picked up by isNotFoundResponse", async () => {
|
|
84
|
+
const filling = new ManduFilling();
|
|
85
|
+
filling.loader(() => notFound({ message: "post gone" }));
|
|
86
|
+
|
|
87
|
+
const ctx = new ManduContext(new Request("http://test/post/1"), { id: "1" });
|
|
88
|
+
const returned = await filling.executeLoader(ctx);
|
|
89
|
+
expect(isNotFoundResponse(returned)).toBe(true);
|
|
90
|
+
// And the body survived.
|
|
91
|
+
const body = await (returned as Response).text();
|
|
92
|
+
expect(body).toBe("post gone");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("loader throwing notFound() is picked up the same way (Remix idiom)", async () => {
|
|
96
|
+
const filling = new ManduFilling();
|
|
97
|
+
filling.loader(() => {
|
|
98
|
+
throw notFound({ message: "thrown" });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const ctx = new ManduContext(new Request("http://test/x"), {});
|
|
102
|
+
let caught: unknown = null;
|
|
103
|
+
try {
|
|
104
|
+
await filling.executeLoader(ctx);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
caught = e;
|
|
107
|
+
}
|
|
108
|
+
expect(isNotFoundResponse(caught)).toBe(true);
|
|
109
|
+
expect(await (caught as Response).text()).toBe("thrown");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("integration simulation: loadPageData-like branch distinguishes return vs throw", async () => {
|
|
113
|
+
// Emulates the exact shape of the SSR pipeline's try/catch: `returned`
|
|
114
|
+
// and `thrown` both route through `isNotFoundResponse`. This is the
|
|
115
|
+
// contract the server integration relies on.
|
|
116
|
+
const returnCase = notFound({ message: "A" });
|
|
117
|
+
const throwCase = notFound({ message: "B" });
|
|
118
|
+
|
|
119
|
+
// Both must be recognised.
|
|
120
|
+
expect(isNotFoundResponse(returnCase)).toBe(true);
|
|
121
|
+
expect(isNotFoundResponse(throwCase)).toBe(true);
|
|
122
|
+
|
|
123
|
+
// And each preserves its own message (no shared mutable state).
|
|
124
|
+
expect(await returnCase.text()).toBe("A");
|
|
125
|
+
expect(await throwCase.text()).toBe("B");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("accepts a call with no arguments and still produces a valid sentinel", () => {
|
|
129
|
+
const res = notFound();
|
|
130
|
+
expect(res).toBeInstanceOf(Response);
|
|
131
|
+
expect(res.status).toBe(404);
|
|
132
|
+
expect(isNotFoundResponse(res)).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("exports NOT_FOUND_BRAND as a unique symbol keyed to the package", () => {
|
|
136
|
+
// The brand must be a Symbol.for() so multiple copies of @mandujs/core
|
|
137
|
+
// (e.g. monorepo duplication) agree on identity. If this changes we've
|
|
138
|
+
// broken cross-package detection — regression guard.
|
|
139
|
+
expect(typeof NOT_FOUND_BRAND).toBe("symbol");
|
|
140
|
+
expect(Symbol.keyFor(NOT_FOUND_BRAND)).toBe("@mandujs/core/not-found");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("two separate notFound() calls produce independently-branded Responses", () => {
|
|
144
|
+
// WeakSet membership is per-instance — the brand on one must not
|
|
145
|
+
// leak to another. Important for test isolation.
|
|
146
|
+
const a = notFound();
|
|
147
|
+
const b = notFound();
|
|
148
|
+
expect(a).not.toBe(b);
|
|
149
|
+
expect(isNotFoundResponse(a)).toBe(true);
|
|
150
|
+
expect(isNotFoundResponse(b)).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
});
|
package/src/runtime/adapter.ts
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu Adapter Interface
|
|
3
|
-
* 런타임 중립적 서버 어댑터 추상화
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { RoutesManifest } from "../spec/schema";
|
|
7
|
-
import type { BundleManifest } from "../bundler/types";
|
|
8
|
-
import type { ServerOptions } from "./server";
|
|
9
|
-
|
|
10
|
-
// ========== Types ==========
|
|
11
|
-
|
|
12
|
-
export interface AdapterOptions {
|
|
13
|
-
manifest: RoutesManifest;
|
|
14
|
-
bundleManifest?: BundleManifest;
|
|
15
|
-
rootDir: string;
|
|
16
|
-
serverOptions: ServerOptions;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface AdapterServer {
|
|
20
|
-
/** fetch handler (Web Fetch API — 런타임 중립) */
|
|
21
|
-
fetch: (request: Request) => Promise<Response>;
|
|
22
|
-
/** 서버 시작 */
|
|
23
|
-
listen(port: number, hostname?: string): Promise<{ port: number; hostname: string }>;
|
|
24
|
-
/** 서버 중지 */
|
|
25
|
-
close(): Promise<void>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Mandu Adapter 인터페이스
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```typescript
|
|
33
|
-
* // mandu.config.ts
|
|
34
|
-
* import adapterBun from "@mandujs/adapter-bun";
|
|
35
|
-
*
|
|
36
|
-
* export default {
|
|
37
|
-
* adapter: adapterBun(),
|
|
38
|
-
* };
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export interface ManduAdapter {
|
|
42
|
-
name: string;
|
|
43
|
-
/** 빌드 타임: 배포 산출물 생성 (SSG, 서버리스 번들 등) */
|
|
44
|
-
build?(options: AdapterOptions): Promise<void>;
|
|
45
|
-
/** 런타임: 서버 인스턴스 생성 */
|
|
46
|
-
createServer(options: AdapterOptions): AdapterServer;
|
|
47
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Adapter Interface
|
|
3
|
+
* 런타임 중립적 서버 어댑터 추상화
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RoutesManifest } from "../spec/schema";
|
|
7
|
+
import type { BundleManifest } from "../bundler/types";
|
|
8
|
+
import type { ServerOptions } from "./server";
|
|
9
|
+
|
|
10
|
+
// ========== Types ==========
|
|
11
|
+
|
|
12
|
+
export interface AdapterOptions {
|
|
13
|
+
manifest: RoutesManifest;
|
|
14
|
+
bundleManifest?: BundleManifest;
|
|
15
|
+
rootDir: string;
|
|
16
|
+
serverOptions: ServerOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AdapterServer {
|
|
20
|
+
/** fetch handler (Web Fetch API — 런타임 중립) */
|
|
21
|
+
fetch: (request: Request) => Promise<Response>;
|
|
22
|
+
/** 서버 시작 */
|
|
23
|
+
listen(port: number, hostname?: string): Promise<{ port: number; hostname: string }>;
|
|
24
|
+
/** 서버 중지 */
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Mandu Adapter 인터페이스
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // mandu.config.ts
|
|
34
|
+
* import adapterBun from "@mandujs/adapter-bun";
|
|
35
|
+
*
|
|
36
|
+
* export default {
|
|
37
|
+
* adapter: adapterBun(),
|
|
38
|
+
* };
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export interface ManduAdapter {
|
|
42
|
+
name: string;
|
|
43
|
+
/** 빌드 타임: 배포 산출물 생성 (SSG, 서버리스 번들 등) */
|
|
44
|
+
build?(options: AdapterOptions): Promise<void>;
|
|
45
|
+
/** 런타임: 서버 인스턴스 생성 */
|
|
46
|
+
createServer(options: AdapterOptions): AdapterServer;
|
|
47
|
+
}
|