@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
package/src/testing/mocks.ts
CHANGED
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @mandujs/core/testing/mocks
|
|
3
|
-
*
|
|
4
|
-
* Swap-in mocks for the I/O primitives tests typically stub:
|
|
5
|
-
*
|
|
6
|
-
* - `mockMail()` → the production `MemoryEmailSender` (Phase 5 email
|
|
7
|
-
* primitive), wrapped so tests get a uniform async-dispose cleanup helper.
|
|
8
|
-
* - `mockStorage()` → an in-memory `S3Client`-shaped handle satisfying the
|
|
9
|
-
* public `@mandujs/core/storage/s3` interface without booting `Bun.S3Client`.
|
|
10
|
-
*
|
|
11
|
-
* Both return objects that are **drop-in replacements** for their production
|
|
12
|
-
* counterparts. Pass them to the handler/service under test via whatever
|
|
13
|
-
* dependency-injection path your code already uses — there is no magic here.
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { mockMail, mockStorage } from "@mandujs/core/testing";
|
|
17
|
-
*
|
|
18
|
-
* const mail = mockMail();
|
|
19
|
-
* const storage = mockStorage();
|
|
20
|
-
*
|
|
21
|
-
* await sendWelcomeEmail({ mail }, "u@x.com");
|
|
22
|
-
* expect(mail.sent[0].subject).toBe("Welcome");
|
|
23
|
-
*
|
|
24
|
-
* await uploadAvatar({ storage }, buffer);
|
|
25
|
-
* expect(await storage.exists("u/avatar.png")).toBe(true);
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* @module testing/mocks
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
createMemoryEmailSender,
|
|
33
|
-
type EmailMessage,
|
|
34
|
-
type MemoryEmailSender,
|
|
35
|
-
} from "../email/index";
|
|
36
|
-
import { getContentType, type S3Client, type S3UploadOptions, type S3PresignOptions } from "../storage/s3/index";
|
|
37
|
-
|
|
38
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
-
// Email mock
|
|
40
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
-
|
|
42
|
-
/** Handle returned by {@link mockMail}. Extends the production sender 1:1. */
|
|
43
|
-
export interface MockMail extends MemoryEmailSender {
|
|
44
|
-
/**
|
|
45
|
-
* Find the most recently sent message whose recipient matches `to`
|
|
46
|
-
* (single-address equality). Returns `undefined` if none matched.
|
|
47
|
-
*
|
|
48
|
-
* Convenience shortcut around a reverse scan of `sent` — the common
|
|
49
|
-
* assertion in verification / password-reset tests.
|
|
50
|
-
*/
|
|
51
|
-
lastTo(to: string): (EmailMessage & { id: string; sentAt: number }) | undefined;
|
|
52
|
-
/** `using mail = mockMail()` — clears the outbox on exit. */
|
|
53
|
-
[Symbol.dispose](): void;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Create an in-process email sender. Backing store is a plain array —
|
|
58
|
-
* read via `mail.sent`, clear between cases with `mail.clear()`.
|
|
59
|
-
*
|
|
60
|
-
* This is a thin wrapper around the production `createMemoryEmailSender`
|
|
61
|
-
* so tests do not depend on an implementation detail of the email package.
|
|
62
|
-
*/
|
|
63
|
-
export function mockMail(): MockMail {
|
|
64
|
-
const inner = createMemoryEmailSender();
|
|
65
|
-
|
|
66
|
-
// MemoryEmailSender's `sent` is declared readonly at the type level; the
|
|
67
|
-
// runtime object exposes .push under the covers. We add the convenience
|
|
68
|
-
// helper without widening the surface.
|
|
69
|
-
//
|
|
70
|
-
// Reverse-iterating by index (vs. `.findLast`) avoids dependency on the
|
|
71
|
-
// ES2023 `Array.prototype.findLast` lib type — older tsconfig targets
|
|
72
|
-
// (`"lib": ["ES2022"]`) do not declare it. The runtime has it regardless.
|
|
73
|
-
return Object.assign(inner, {
|
|
74
|
-
lastTo(
|
|
75
|
-
to: string,
|
|
76
|
-
): (EmailMessage & { id: string; sentAt: number }) | undefined {
|
|
77
|
-
for (let i = inner.sent.length - 1; i >= 0; i--) {
|
|
78
|
-
const m = inner.sent[i];
|
|
79
|
-
if (Array.isArray(m.to)) {
|
|
80
|
-
if (m.to.includes(to)) return m;
|
|
81
|
-
} else if (m.to === to) {
|
|
82
|
-
return m;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return undefined;
|
|
86
|
-
},
|
|
87
|
-
[Symbol.dispose]() {
|
|
88
|
-
inner.clear();
|
|
89
|
-
},
|
|
90
|
-
}) as MockMail;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
94
|
-
// Storage (S3-compatible) mock
|
|
95
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
96
|
-
|
|
97
|
-
/** Stored blob + metadata for assertions. */
|
|
98
|
-
export interface MockStoredObject {
|
|
99
|
-
readonly body: Uint8Array;
|
|
100
|
-
readonly contentType: string;
|
|
101
|
-
readonly acl?: "private" | "public-read";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Handle returned by {@link mockStorage}. Extends `S3Client` with test-only affordances. */
|
|
105
|
-
export interface MockStorage extends S3Client {
|
|
106
|
-
/** Every key currently present. */
|
|
107
|
-
keys(): string[];
|
|
108
|
-
/** Raw access to a stored object. Returns `undefined` for missing keys. */
|
|
109
|
-
peek(key: string): MockStoredObject | undefined;
|
|
110
|
-
/** Wipe all stored objects. */
|
|
111
|
-
clear(): void;
|
|
112
|
-
/** `using s = mockStorage()` — clears the store on exit. */
|
|
113
|
-
[Symbol.dispose](): void;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Convert the production `S3Client` body types into a normalized Uint8Array
|
|
118
|
-
* so `peek()` returns a stable type regardless of what callers uploaded.
|
|
119
|
-
*/
|
|
120
|
-
function normalizeBody(body: Blob | ArrayBuffer | Uint8Array): Promise<Uint8Array> {
|
|
121
|
-
if (body instanceof Uint8Array) return Promise.resolve(new Uint8Array(body));
|
|
122
|
-
if (body instanceof ArrayBuffer) return Promise.resolve(new Uint8Array(body));
|
|
123
|
-
// Blob → ArrayBuffer → Uint8Array.
|
|
124
|
-
return body.arrayBuffer().then((ab) => new Uint8Array(ab));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Create an in-memory S3-compatible storage client. Satisfies the full
|
|
129
|
-
* `S3Client` interface — handlers written against the production API can
|
|
130
|
-
* be called with this mock unchanged.
|
|
131
|
-
*
|
|
132
|
-
* Presigned URLs are synthesized as opaque `mandu-mock://bucket/<key>`
|
|
133
|
-
* strings. They are not de-serializable back into real uploads — only used
|
|
134
|
-
* for identity assertions (presign returned? key matches?).
|
|
135
|
-
*/
|
|
136
|
-
export function mockStorage(options?: { bucket?: string }): MockStorage {
|
|
137
|
-
const bucket = options?.bucket ?? "mandu-test-bucket";
|
|
138
|
-
const store = new Map<string, MockStoredObject>();
|
|
139
|
-
|
|
140
|
-
async function upload(
|
|
141
|
-
body: Blob | ArrayBuffer | Uint8Array,
|
|
142
|
-
opts: S3UploadOptions,
|
|
143
|
-
): Promise<string> {
|
|
144
|
-
if (!opts.key) {
|
|
145
|
-
throw new TypeError(
|
|
146
|
-
"[testing/mocks] mockStorage.upload: 'key' is required.",
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
const contentType = opts.contentType ?? getContentType(opts.key);
|
|
150
|
-
const bytes = await normalizeBody(body);
|
|
151
|
-
store.set(opts.key, { body: bytes, contentType, acl: opts.acl });
|
|
152
|
-
return `mandu-mock://${bucket}/${opts.key}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function presign(opts: S3PresignOptions): Promise<string> {
|
|
156
|
-
if (!opts.key) {
|
|
157
|
-
throw new TypeError(
|
|
158
|
-
"[testing/mocks] mockStorage.presign: 'key' is required.",
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
const method = opts.method ?? "PUT";
|
|
162
|
-
const expiresIn = opts.expiresIn ?? 900;
|
|
163
|
-
return `mandu-mock://${bucket}/${opts.key}?method=${method}&expires=${expiresIn}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function deleteObject(key: string): Promise<void> {
|
|
167
|
-
store.delete(key);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function getReadable(key: string): Promise<ReadableStream> {
|
|
171
|
-
const obj = store.get(key);
|
|
172
|
-
if (!obj) {
|
|
173
|
-
throw new Error(
|
|
174
|
-
`[testing/mocks] mockStorage.getReadable: key not found: ${JSON.stringify(key)}`,
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
return new ReadableStream({
|
|
178
|
-
start(controller) {
|
|
179
|
-
controller.enqueue(obj.body);
|
|
180
|
-
controller.close();
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async function exists(key: string): Promise<boolean> {
|
|
186
|
-
return store.has(key);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const handle: MockStorage = {
|
|
190
|
-
upload,
|
|
191
|
-
presign,
|
|
192
|
-
delete: deleteObject,
|
|
193
|
-
getReadable,
|
|
194
|
-
exists,
|
|
195
|
-
keys: () => [...store.keys()],
|
|
196
|
-
peek: (key) => store.get(key),
|
|
197
|
-
clear: () => store.clear(),
|
|
198
|
-
[Symbol.dispose]() {
|
|
199
|
-
store.clear();
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
return handle;
|
|
203
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @mandujs/core/testing/mocks
|
|
3
|
+
*
|
|
4
|
+
* Swap-in mocks for the I/O primitives tests typically stub:
|
|
5
|
+
*
|
|
6
|
+
* - `mockMail()` → the production `MemoryEmailSender` (Phase 5 email
|
|
7
|
+
* primitive), wrapped so tests get a uniform async-dispose cleanup helper.
|
|
8
|
+
* - `mockStorage()` → an in-memory `S3Client`-shaped handle satisfying the
|
|
9
|
+
* public `@mandujs/core/storage/s3` interface without booting `Bun.S3Client`.
|
|
10
|
+
*
|
|
11
|
+
* Both return objects that are **drop-in replacements** for their production
|
|
12
|
+
* counterparts. Pass them to the handler/service under test via whatever
|
|
13
|
+
* dependency-injection path your code already uses — there is no magic here.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { mockMail, mockStorage } from "@mandujs/core/testing";
|
|
17
|
+
*
|
|
18
|
+
* const mail = mockMail();
|
|
19
|
+
* const storage = mockStorage();
|
|
20
|
+
*
|
|
21
|
+
* await sendWelcomeEmail({ mail }, "u@x.com");
|
|
22
|
+
* expect(mail.sent[0].subject).toBe("Welcome");
|
|
23
|
+
*
|
|
24
|
+
* await uploadAvatar({ storage }, buffer);
|
|
25
|
+
* expect(await storage.exists("u/avatar.png")).toBe(true);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @module testing/mocks
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
createMemoryEmailSender,
|
|
33
|
+
type EmailMessage,
|
|
34
|
+
type MemoryEmailSender,
|
|
35
|
+
} from "../email/index";
|
|
36
|
+
import { getContentType, type S3Client, type S3UploadOptions, type S3PresignOptions } from "../storage/s3/index";
|
|
37
|
+
|
|
38
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
// Email mock
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
|
|
42
|
+
/** Handle returned by {@link mockMail}. Extends the production sender 1:1. */
|
|
43
|
+
export interface MockMail extends MemoryEmailSender {
|
|
44
|
+
/**
|
|
45
|
+
* Find the most recently sent message whose recipient matches `to`
|
|
46
|
+
* (single-address equality). Returns `undefined` if none matched.
|
|
47
|
+
*
|
|
48
|
+
* Convenience shortcut around a reverse scan of `sent` — the common
|
|
49
|
+
* assertion in verification / password-reset tests.
|
|
50
|
+
*/
|
|
51
|
+
lastTo(to: string): (EmailMessage & { id: string; sentAt: number }) | undefined;
|
|
52
|
+
/** `using mail = mockMail()` — clears the outbox on exit. */
|
|
53
|
+
[Symbol.dispose](): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create an in-process email sender. Backing store is a plain array —
|
|
58
|
+
* read via `mail.sent`, clear between cases with `mail.clear()`.
|
|
59
|
+
*
|
|
60
|
+
* This is a thin wrapper around the production `createMemoryEmailSender`
|
|
61
|
+
* so tests do not depend on an implementation detail of the email package.
|
|
62
|
+
*/
|
|
63
|
+
export function mockMail(): MockMail {
|
|
64
|
+
const inner = createMemoryEmailSender();
|
|
65
|
+
|
|
66
|
+
// MemoryEmailSender's `sent` is declared readonly at the type level; the
|
|
67
|
+
// runtime object exposes .push under the covers. We add the convenience
|
|
68
|
+
// helper without widening the surface.
|
|
69
|
+
//
|
|
70
|
+
// Reverse-iterating by index (vs. `.findLast`) avoids dependency on the
|
|
71
|
+
// ES2023 `Array.prototype.findLast` lib type — older tsconfig targets
|
|
72
|
+
// (`"lib": ["ES2022"]`) do not declare it. The runtime has it regardless.
|
|
73
|
+
return Object.assign(inner, {
|
|
74
|
+
lastTo(
|
|
75
|
+
to: string,
|
|
76
|
+
): (EmailMessage & { id: string; sentAt: number }) | undefined {
|
|
77
|
+
for (let i = inner.sent.length - 1; i >= 0; i--) {
|
|
78
|
+
const m = inner.sent[i];
|
|
79
|
+
if (Array.isArray(m.to)) {
|
|
80
|
+
if (m.to.includes(to)) return m;
|
|
81
|
+
} else if (m.to === to) {
|
|
82
|
+
return m;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
},
|
|
87
|
+
[Symbol.dispose]() {
|
|
88
|
+
inner.clear();
|
|
89
|
+
},
|
|
90
|
+
}) as MockMail;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
94
|
+
// Storage (S3-compatible) mock
|
|
95
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
96
|
+
|
|
97
|
+
/** Stored blob + metadata for assertions. */
|
|
98
|
+
export interface MockStoredObject {
|
|
99
|
+
readonly body: Uint8Array;
|
|
100
|
+
readonly contentType: string;
|
|
101
|
+
readonly acl?: "private" | "public-read";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Handle returned by {@link mockStorage}. Extends `S3Client` with test-only affordances. */
|
|
105
|
+
export interface MockStorage extends S3Client {
|
|
106
|
+
/** Every key currently present. */
|
|
107
|
+
keys(): string[];
|
|
108
|
+
/** Raw access to a stored object. Returns `undefined` for missing keys. */
|
|
109
|
+
peek(key: string): MockStoredObject | undefined;
|
|
110
|
+
/** Wipe all stored objects. */
|
|
111
|
+
clear(): void;
|
|
112
|
+
/** `using s = mockStorage()` — clears the store on exit. */
|
|
113
|
+
[Symbol.dispose](): void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert the production `S3Client` body types into a normalized Uint8Array
|
|
118
|
+
* so `peek()` returns a stable type regardless of what callers uploaded.
|
|
119
|
+
*/
|
|
120
|
+
function normalizeBody(body: Blob | ArrayBuffer | Uint8Array): Promise<Uint8Array> {
|
|
121
|
+
if (body instanceof Uint8Array) return Promise.resolve(new Uint8Array(body));
|
|
122
|
+
if (body instanceof ArrayBuffer) return Promise.resolve(new Uint8Array(body));
|
|
123
|
+
// Blob → ArrayBuffer → Uint8Array.
|
|
124
|
+
return body.arrayBuffer().then((ab) => new Uint8Array(ab));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create an in-memory S3-compatible storage client. Satisfies the full
|
|
129
|
+
* `S3Client` interface — handlers written against the production API can
|
|
130
|
+
* be called with this mock unchanged.
|
|
131
|
+
*
|
|
132
|
+
* Presigned URLs are synthesized as opaque `mandu-mock://bucket/<key>`
|
|
133
|
+
* strings. They are not de-serializable back into real uploads — only used
|
|
134
|
+
* for identity assertions (presign returned? key matches?).
|
|
135
|
+
*/
|
|
136
|
+
export function mockStorage(options?: { bucket?: string }): MockStorage {
|
|
137
|
+
const bucket = options?.bucket ?? "mandu-test-bucket";
|
|
138
|
+
const store = new Map<string, MockStoredObject>();
|
|
139
|
+
|
|
140
|
+
async function upload(
|
|
141
|
+
body: Blob | ArrayBuffer | Uint8Array,
|
|
142
|
+
opts: S3UploadOptions,
|
|
143
|
+
): Promise<string> {
|
|
144
|
+
if (!opts.key) {
|
|
145
|
+
throw new TypeError(
|
|
146
|
+
"[testing/mocks] mockStorage.upload: 'key' is required.",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const contentType = opts.contentType ?? getContentType(opts.key);
|
|
150
|
+
const bytes = await normalizeBody(body);
|
|
151
|
+
store.set(opts.key, { body: bytes, contentType, acl: opts.acl });
|
|
152
|
+
return `mandu-mock://${bucket}/${opts.key}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function presign(opts: S3PresignOptions): Promise<string> {
|
|
156
|
+
if (!opts.key) {
|
|
157
|
+
throw new TypeError(
|
|
158
|
+
"[testing/mocks] mockStorage.presign: 'key' is required.",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const method = opts.method ?? "PUT";
|
|
162
|
+
const expiresIn = opts.expiresIn ?? 900;
|
|
163
|
+
return `mandu-mock://${bucket}/${opts.key}?method=${method}&expires=${expiresIn}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function deleteObject(key: string): Promise<void> {
|
|
167
|
+
store.delete(key);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function getReadable(key: string): Promise<ReadableStream> {
|
|
171
|
+
const obj = store.get(key);
|
|
172
|
+
if (!obj) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`[testing/mocks] mockStorage.getReadable: key not found: ${JSON.stringify(key)}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return new ReadableStream({
|
|
178
|
+
start(controller) {
|
|
179
|
+
controller.enqueue(obj.body);
|
|
180
|
+
controller.close();
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function exists(key: string): Promise<boolean> {
|
|
186
|
+
return store.has(key);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const handle: MockStorage = {
|
|
190
|
+
upload,
|
|
191
|
+
presign,
|
|
192
|
+
delete: deleteObject,
|
|
193
|
+
getReadable,
|
|
194
|
+
exists,
|
|
195
|
+
keys: () => [...store.keys()],
|
|
196
|
+
peek: (key) => store.get(key),
|
|
197
|
+
clear: () => store.clear(),
|
|
198
|
+
[Symbol.dispose]() {
|
|
199
|
+
store.clear();
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
return handle;
|
|
203
|
+
}
|