@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/a11y/types.ts
CHANGED
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @mandujs/core/a11y — audit result types.
|
|
3
|
-
*
|
|
4
|
-
* Phase 18.χ introduces a framework-level accessibility guardrail that
|
|
5
|
-
* runs axe-core against prerendered HTML and aggregates WCAG violations
|
|
6
|
-
* into a structured report. axe-core and jsdom are **optional peer
|
|
7
|
-
* dependencies** — we never install them for the user. When absent, the
|
|
8
|
-
* runner degrades gracefully to an `"axe-missing"` outcome instead of
|
|
9
|
-
* throwing or bundling ~1 MB of rules into every consumer.
|
|
10
|
-
*
|
|
11
|
-
* Severity mapping matches axe-core's `impact` scale verbatim so CI
|
|
12
|
-
* gates using `--audit-fail-on=<impact>` speak the same vocabulary as
|
|
13
|
-
* the axe documentation:
|
|
14
|
-
*
|
|
15
|
-
* - `minor` — nice-to-fix cosmetic a11y issue
|
|
16
|
-
* - `moderate` — noticeable UX degradation
|
|
17
|
-
* - `serious` — significant barrier for users with disabilities
|
|
18
|
-
* - `critical` — blocks assistive-tech users outright (default gate)
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** axe-core impact scale (lowest → highest). */
|
|
22
|
-
export type AuditImpact = "minor" | "moderate" | "serious" | "critical";
|
|
23
|
-
|
|
24
|
-
/** Canonical ordering used by severity comparisons. */
|
|
25
|
-
export const AUDIT_IMPACT_ORDER: readonly AuditImpact[] = [
|
|
26
|
-
"minor",
|
|
27
|
-
"moderate",
|
|
28
|
-
"serious",
|
|
29
|
-
"critical",
|
|
30
|
-
] as const;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Return `true` when `candidate` is at least as severe as `threshold`.
|
|
34
|
-
* Used by both the runner (severity filter) and the CLI gate
|
|
35
|
-
* (`--audit-fail-on`).
|
|
36
|
-
*/
|
|
37
|
-
export function impactAtLeast(candidate: AuditImpact | null | undefined, threshold: AuditImpact): boolean {
|
|
38
|
-
if (!candidate) return false;
|
|
39
|
-
const ci = AUDIT_IMPACT_ORDER.indexOf(candidate);
|
|
40
|
-
const ti = AUDIT_IMPACT_ORDER.indexOf(threshold);
|
|
41
|
-
return ci >= 0 && ti >= 0 && ci >= ti;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Per-node failure detail. Keeps payloads small — we intentionally
|
|
45
|
-
* discard axe's full `any`/`all`/`none` check trees and keep only the
|
|
46
|
-
* fields humans act on (selector + failure summary). */
|
|
47
|
-
export interface AuditNode {
|
|
48
|
-
/** CSS selector chain axe-core emits (e.g. `html > body > div#root`). */
|
|
49
|
-
target: string;
|
|
50
|
-
/** axe's `failureSummary` — already-localized multi-line description. */
|
|
51
|
-
failureSummary: string;
|
|
52
|
-
/** Raw HTML snippet for the offending node (truncated to 300 chars). */
|
|
53
|
-
html?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** One WCAG violation emitted by axe-core, scoped to a single HTML file. */
|
|
57
|
-
export interface AuditViolation {
|
|
58
|
-
/** Source HTML file (absolute path). */
|
|
59
|
-
file: string;
|
|
60
|
-
/** axe-core rule id, e.g. `color-contrast`, `label`, `image-alt`. */
|
|
61
|
-
rule: string;
|
|
62
|
-
/** Severity — may be `null` when axe fails to classify (rare). */
|
|
63
|
-
impact: AuditImpact | null;
|
|
64
|
-
/** One-line human-readable rule summary (`node.help`). */
|
|
65
|
-
help: string;
|
|
66
|
-
/** URL to axe-core's documentation for this rule. */
|
|
67
|
-
helpUrl?: string;
|
|
68
|
-
/** Offending DOM nodes. Capped at 10 to keep reports consumable. */
|
|
69
|
-
nodes: AuditNode[];
|
|
70
|
-
/** Phase 18.χ hint — short actionable fix recipe when we recognize the rule. */
|
|
71
|
-
fixHint?: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Audit outcome. The three arms are mutually exclusive:
|
|
76
|
-
*
|
|
77
|
-
* - `ok` — runner executed, zero violations ≥ minImpact.
|
|
78
|
-
* - `violations` — runner executed, at least one violation fired.
|
|
79
|
-
* - `axe-missing` — optional dep not installed; runner was a no-op.
|
|
80
|
-
*
|
|
81
|
-
* The `filesScanned` counter is always present so callers can print a
|
|
82
|
-
* meaningful summary ("audited 12 files, 0 violations") regardless of
|
|
83
|
-
* outcome.
|
|
84
|
-
*/
|
|
85
|
-
export interface AuditReport {
|
|
86
|
-
outcome: "ok" | "violations" | "axe-missing";
|
|
87
|
-
/** Number of HTML files actually fed to axe-core (0 when dep missing). */
|
|
88
|
-
filesScanned: number;
|
|
89
|
-
/** Aggregated violations across every file. Empty when `outcome !== "violations"`. */
|
|
90
|
-
violations: AuditViolation[];
|
|
91
|
-
/** Count of violations at each impact level. */
|
|
92
|
-
impactCounts: Record<AuditImpact, number>;
|
|
93
|
-
/** Effective severity filter applied during this run. */
|
|
94
|
-
minImpact: AuditImpact;
|
|
95
|
-
/** Optional human-readable note (e.g. why the runner skipped). */
|
|
96
|
-
note?: string;
|
|
97
|
-
/** Elapsed wallclock ms. Zero when runner was a no-op. */
|
|
98
|
-
durationMs: number;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Options accepted by `runAudit`. */
|
|
102
|
-
export interface RunAuditOptions {
|
|
103
|
-
/**
|
|
104
|
-
* Minimum severity to include in the report. Violations below this
|
|
105
|
-
* threshold are dropped at aggregation time. Default `"minor"`.
|
|
106
|
-
*/
|
|
107
|
-
minImpact?: AuditImpact;
|
|
108
|
-
/**
|
|
109
|
-
* Cap on files to audit. Prevents catastrophic CI runs on projects
|
|
110
|
-
* that accidentally prerender thousands of routes. Default `500`.
|
|
111
|
-
*/
|
|
112
|
-
maxFiles?: number;
|
|
113
|
-
/**
|
|
114
|
-
* Override for axe-core module resolution. When undefined, the runner
|
|
115
|
-
* uses dynamic `import("axe-core")`. Tests inject a fixture instead
|
|
116
|
-
* of shimming the module resolver.
|
|
117
|
-
*/
|
|
118
|
-
axeLoader?: () => Promise<unknown>;
|
|
119
|
-
/**
|
|
120
|
-
* Override for jsdom module resolution. Same contract as `axeLoader`;
|
|
121
|
-
* returning `null` forces the runner to fall back to HappyDOM which is
|
|
122
|
-
* already a transitive test-time dep for Mandu.
|
|
123
|
-
*/
|
|
124
|
-
domLoader?: () => Promise<unknown>;
|
|
125
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @mandujs/core/a11y — audit result types.
|
|
3
|
+
*
|
|
4
|
+
* Phase 18.χ introduces a framework-level accessibility guardrail that
|
|
5
|
+
* runs axe-core against prerendered HTML and aggregates WCAG violations
|
|
6
|
+
* into a structured report. axe-core and jsdom are **optional peer
|
|
7
|
+
* dependencies** — we never install them for the user. When absent, the
|
|
8
|
+
* runner degrades gracefully to an `"axe-missing"` outcome instead of
|
|
9
|
+
* throwing or bundling ~1 MB of rules into every consumer.
|
|
10
|
+
*
|
|
11
|
+
* Severity mapping matches axe-core's `impact` scale verbatim so CI
|
|
12
|
+
* gates using `--audit-fail-on=<impact>` speak the same vocabulary as
|
|
13
|
+
* the axe documentation:
|
|
14
|
+
*
|
|
15
|
+
* - `minor` — nice-to-fix cosmetic a11y issue
|
|
16
|
+
* - `moderate` — noticeable UX degradation
|
|
17
|
+
* - `serious` — significant barrier for users with disabilities
|
|
18
|
+
* - `critical` — blocks assistive-tech users outright (default gate)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** axe-core impact scale (lowest → highest). */
|
|
22
|
+
export type AuditImpact = "minor" | "moderate" | "serious" | "critical";
|
|
23
|
+
|
|
24
|
+
/** Canonical ordering used by severity comparisons. */
|
|
25
|
+
export const AUDIT_IMPACT_ORDER: readonly AuditImpact[] = [
|
|
26
|
+
"minor",
|
|
27
|
+
"moderate",
|
|
28
|
+
"serious",
|
|
29
|
+
"critical",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return `true` when `candidate` is at least as severe as `threshold`.
|
|
34
|
+
* Used by both the runner (severity filter) and the CLI gate
|
|
35
|
+
* (`--audit-fail-on`).
|
|
36
|
+
*/
|
|
37
|
+
export function impactAtLeast(candidate: AuditImpact | null | undefined, threshold: AuditImpact): boolean {
|
|
38
|
+
if (!candidate) return false;
|
|
39
|
+
const ci = AUDIT_IMPACT_ORDER.indexOf(candidate);
|
|
40
|
+
const ti = AUDIT_IMPACT_ORDER.indexOf(threshold);
|
|
41
|
+
return ci >= 0 && ti >= 0 && ci >= ti;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Per-node failure detail. Keeps payloads small — we intentionally
|
|
45
|
+
* discard axe's full `any`/`all`/`none` check trees and keep only the
|
|
46
|
+
* fields humans act on (selector + failure summary). */
|
|
47
|
+
export interface AuditNode {
|
|
48
|
+
/** CSS selector chain axe-core emits (e.g. `html > body > div#root`). */
|
|
49
|
+
target: string;
|
|
50
|
+
/** axe's `failureSummary` — already-localized multi-line description. */
|
|
51
|
+
failureSummary: string;
|
|
52
|
+
/** Raw HTML snippet for the offending node (truncated to 300 chars). */
|
|
53
|
+
html?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One WCAG violation emitted by axe-core, scoped to a single HTML file. */
|
|
57
|
+
export interface AuditViolation {
|
|
58
|
+
/** Source HTML file (absolute path). */
|
|
59
|
+
file: string;
|
|
60
|
+
/** axe-core rule id, e.g. `color-contrast`, `label`, `image-alt`. */
|
|
61
|
+
rule: string;
|
|
62
|
+
/** Severity — may be `null` when axe fails to classify (rare). */
|
|
63
|
+
impact: AuditImpact | null;
|
|
64
|
+
/** One-line human-readable rule summary (`node.help`). */
|
|
65
|
+
help: string;
|
|
66
|
+
/** URL to axe-core's documentation for this rule. */
|
|
67
|
+
helpUrl?: string;
|
|
68
|
+
/** Offending DOM nodes. Capped at 10 to keep reports consumable. */
|
|
69
|
+
nodes: AuditNode[];
|
|
70
|
+
/** Phase 18.χ hint — short actionable fix recipe when we recognize the rule. */
|
|
71
|
+
fixHint?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Audit outcome. The three arms are mutually exclusive:
|
|
76
|
+
*
|
|
77
|
+
* - `ok` — runner executed, zero violations ≥ minImpact.
|
|
78
|
+
* - `violations` — runner executed, at least one violation fired.
|
|
79
|
+
* - `axe-missing` — optional dep not installed; runner was a no-op.
|
|
80
|
+
*
|
|
81
|
+
* The `filesScanned` counter is always present so callers can print a
|
|
82
|
+
* meaningful summary ("audited 12 files, 0 violations") regardless of
|
|
83
|
+
* outcome.
|
|
84
|
+
*/
|
|
85
|
+
export interface AuditReport {
|
|
86
|
+
outcome: "ok" | "violations" | "axe-missing";
|
|
87
|
+
/** Number of HTML files actually fed to axe-core (0 when dep missing). */
|
|
88
|
+
filesScanned: number;
|
|
89
|
+
/** Aggregated violations across every file. Empty when `outcome !== "violations"`. */
|
|
90
|
+
violations: AuditViolation[];
|
|
91
|
+
/** Count of violations at each impact level. */
|
|
92
|
+
impactCounts: Record<AuditImpact, number>;
|
|
93
|
+
/** Effective severity filter applied during this run. */
|
|
94
|
+
minImpact: AuditImpact;
|
|
95
|
+
/** Optional human-readable note (e.g. why the runner skipped). */
|
|
96
|
+
note?: string;
|
|
97
|
+
/** Elapsed wallclock ms. Zero when runner was a no-op. */
|
|
98
|
+
durationMs: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Options accepted by `runAudit`. */
|
|
102
|
+
export interface RunAuditOptions {
|
|
103
|
+
/**
|
|
104
|
+
* Minimum severity to include in the report. Violations below this
|
|
105
|
+
* threshold are dropped at aggregation time. Default `"minor"`.
|
|
106
|
+
*/
|
|
107
|
+
minImpact?: AuditImpact;
|
|
108
|
+
/**
|
|
109
|
+
* Cap on files to audit. Prevents catastrophic CI runs on projects
|
|
110
|
+
* that accidentally prerender thousands of routes. Default `500`.
|
|
111
|
+
*/
|
|
112
|
+
maxFiles?: number;
|
|
113
|
+
/**
|
|
114
|
+
* Override for axe-core module resolution. When undefined, the runner
|
|
115
|
+
* uses dynamic `import("axe-core")`. Tests inject a fixture instead
|
|
116
|
+
* of shimming the module resolver.
|
|
117
|
+
*/
|
|
118
|
+
axeLoader?: () => Promise<unknown>;
|
|
119
|
+
/**
|
|
120
|
+
* Override for jsdom module resolution. Same contract as `axeLoader`;
|
|
121
|
+
* returning `null` forces the runner to fall back to HappyDOM which is
|
|
122
|
+
* already a transitive test-time dep for Mandu.
|
|
123
|
+
*/
|
|
124
|
+
domLoader?: () => Promise<unknown>;
|
|
125
|
+
}
|
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @mandujs/core/auth/password tests
|
|
3
|
-
*
|
|
4
|
-
* These tests exercise real argon2id and bcrypt hashing through `Bun.password`.
|
|
5
|
-
* They are CPU-bound (~50-200ms per hash op on laptop hardware); the suite
|
|
6
|
-
* uses the minimum cost parameters Bun accepts whenever the test does not
|
|
7
|
-
* care about actual KDF strength. Total runtime ~2-5s is expected.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect } from "bun:test";
|
|
11
|
-
import { hashPassword, verifyPassword } from "../password";
|
|
12
|
-
|
|
13
|
-
// Argon2 hashes start with "$argon2id$", "$argon2d$", or "$argon2i$".
|
|
14
|
-
const ARGON2ID_PREFIX = "$argon2id$";
|
|
15
|
-
// bcrypt hashes use $2a$/$2b$/$2y$ prefixes; Bun emits $2b$.
|
|
16
|
-
const BCRYPT_PREFIX_RE = /^\$2[aby]\$/;
|
|
17
|
-
|
|
18
|
-
// Minimum argon2 cost params — keep tests fast without changing behavior.
|
|
19
|
-
const FAST_ARGON2 = {
|
|
20
|
-
algorithm: "argon2id",
|
|
21
|
-
memoryCost: 4,
|
|
22
|
-
timeCost: 2,
|
|
23
|
-
} as const;
|
|
24
|
-
|
|
25
|
-
describe("@mandujs/core/auth/password — hashPassword", () => {
|
|
26
|
-
it("produces an argon2id hash by default", async () => {
|
|
27
|
-
const h = await hashPassword("hunter2", FAST_ARGON2);
|
|
28
|
-
expect(h.startsWith(ARGON2ID_PREFIX)).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("produces a bcrypt hash when algorithm=bcrypt is requested", async () => {
|
|
32
|
-
const h = await hashPassword("hunter2", { algorithm: "bcrypt", cost: 4 });
|
|
33
|
-
expect(h).toMatch(BCRYPT_PREFIX_RE);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("throws on empty plaintext", async () => {
|
|
37
|
-
await expect(hashPassword("", FAST_ARGON2)).rejects.toThrow(
|
|
38
|
-
/non-empty string/,
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("throws when bcrypt input exceeds 72 bytes", async () => {
|
|
43
|
-
// 73 ASCII bytes (each char = 1 UTF-8 byte).
|
|
44
|
-
const tooLong = "a".repeat(73);
|
|
45
|
-
await expect(
|
|
46
|
-
hashPassword(tooLong, { algorithm: "bcrypt", cost: 4 }),
|
|
47
|
-
).rejects.toThrow(/72-byte limit/);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("accepts >72-byte input when using argon2id (bcrypt-only limit)", async () => {
|
|
51
|
-
const longButFine = "a".repeat(100);
|
|
52
|
-
const h = await hashPassword(longButFine, FAST_ARGON2);
|
|
53
|
-
expect(h.startsWith(ARGON2ID_PREFIX)).toBe(true);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("counts UTF-8 bytes (not code points) when enforcing the bcrypt limit", async () => {
|
|
57
|
-
// "é" is 2 bytes in UTF-8 — 40 of them = 80 bytes, over the 72 limit.
|
|
58
|
-
const multibyte = "é".repeat(40);
|
|
59
|
-
await expect(
|
|
60
|
-
hashPassword(multibyte, { algorithm: "bcrypt", cost: 4 }),
|
|
61
|
-
).rejects.toThrow(/72-byte limit/);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("honors non-default argon2 timeCost via option pass-through", async () => {
|
|
65
|
-
// Bun embeds the cost parameters in the hash string:
|
|
66
|
-
// $argon2id$v=19$m=<memoryCost>,t=<timeCost>,p=1$<salt>$<hash>
|
|
67
|
-
// We assert the timeCost is preserved end-to-end.
|
|
68
|
-
const h = await hashPassword("same-password", {
|
|
69
|
-
algorithm: "argon2id",
|
|
70
|
-
memoryCost: 4,
|
|
71
|
-
timeCost: 3,
|
|
72
|
-
});
|
|
73
|
-
expect(h).toMatch(/\$argon2id\$v=\d+\$m=4,t=3,p=\d+\$/);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe("@mandujs/core/auth/password — verifyPassword", () => {
|
|
78
|
-
it("returns true for a matching argon2id roundtrip", async () => {
|
|
79
|
-
const h = await hashPassword("correct-horse-battery-staple", FAST_ARGON2);
|
|
80
|
-
const ok = await verifyPassword("correct-horse-battery-staple", h);
|
|
81
|
-
expect(ok).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("returns true for a matching bcrypt roundtrip", async () => {
|
|
85
|
-
const h = await hashPassword("correct-horse-battery-staple", {
|
|
86
|
-
algorithm: "bcrypt",
|
|
87
|
-
cost: 4,
|
|
88
|
-
});
|
|
89
|
-
const ok = await verifyPassword("correct-horse-battery-staple", h);
|
|
90
|
-
expect(ok).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("returns false for wrong plaintext", async () => {
|
|
94
|
-
const h = await hashPassword("correct", FAST_ARGON2);
|
|
95
|
-
expect(await verifyPassword("incorrect", h)).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("returns false for a tampered hash (one char mutated)", async () => {
|
|
99
|
-
const h = await hashPassword("correct", FAST_ARGON2);
|
|
100
|
-
// Flip the last character of the base64 hash segment.
|
|
101
|
-
const last = h.charAt(h.length - 1);
|
|
102
|
-
const replacement = last === "A" ? "B" : "A";
|
|
103
|
-
const tampered = h.slice(0, -1) + replacement;
|
|
104
|
-
expect(tampered).not.toBe(h);
|
|
105
|
-
expect(await verifyPassword("correct", tampered)).toBe(false);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("returns false (never throws) for a malformed hash string", async () => {
|
|
109
|
-
// Bun.password.verify throws on unparseable hashes; our wrapper collapses
|
|
110
|
-
// every failure mode to `false` so login handlers stay branchless.
|
|
111
|
-
expect(await verifyPassword("anything", "not-a-hash")).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("returns false for empty plaintext", async () => {
|
|
115
|
-
const h = await hashPassword("correct", FAST_ARGON2);
|
|
116
|
-
expect(await verifyPassword("", h)).toBe(false);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("returns false for empty hash", async () => {
|
|
120
|
-
expect(await verifyPassword("correct", "")).toBe(false);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @mandujs/core/auth/password tests
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise real argon2id and bcrypt hashing through `Bun.password`.
|
|
5
|
+
* They are CPU-bound (~50-200ms per hash op on laptop hardware); the suite
|
|
6
|
+
* uses the minimum cost parameters Bun accepts whenever the test does not
|
|
7
|
+
* care about actual KDF strength. Total runtime ~2-5s is expected.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from "bun:test";
|
|
11
|
+
import { hashPassword, verifyPassword } from "../password";
|
|
12
|
+
|
|
13
|
+
// Argon2 hashes start with "$argon2id$", "$argon2d$", or "$argon2i$".
|
|
14
|
+
const ARGON2ID_PREFIX = "$argon2id$";
|
|
15
|
+
// bcrypt hashes use $2a$/$2b$/$2y$ prefixes; Bun emits $2b$.
|
|
16
|
+
const BCRYPT_PREFIX_RE = /^\$2[aby]\$/;
|
|
17
|
+
|
|
18
|
+
// Minimum argon2 cost params — keep tests fast without changing behavior.
|
|
19
|
+
const FAST_ARGON2 = {
|
|
20
|
+
algorithm: "argon2id",
|
|
21
|
+
memoryCost: 4,
|
|
22
|
+
timeCost: 2,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
describe("@mandujs/core/auth/password — hashPassword", () => {
|
|
26
|
+
it("produces an argon2id hash by default", async () => {
|
|
27
|
+
const h = await hashPassword("hunter2", FAST_ARGON2);
|
|
28
|
+
expect(h.startsWith(ARGON2ID_PREFIX)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("produces a bcrypt hash when algorithm=bcrypt is requested", async () => {
|
|
32
|
+
const h = await hashPassword("hunter2", { algorithm: "bcrypt", cost: 4 });
|
|
33
|
+
expect(h).toMatch(BCRYPT_PREFIX_RE);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("throws on empty plaintext", async () => {
|
|
37
|
+
await expect(hashPassword("", FAST_ARGON2)).rejects.toThrow(
|
|
38
|
+
/non-empty string/,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("throws when bcrypt input exceeds 72 bytes", async () => {
|
|
43
|
+
// 73 ASCII bytes (each char = 1 UTF-8 byte).
|
|
44
|
+
const tooLong = "a".repeat(73);
|
|
45
|
+
await expect(
|
|
46
|
+
hashPassword(tooLong, { algorithm: "bcrypt", cost: 4 }),
|
|
47
|
+
).rejects.toThrow(/72-byte limit/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("accepts >72-byte input when using argon2id (bcrypt-only limit)", async () => {
|
|
51
|
+
const longButFine = "a".repeat(100);
|
|
52
|
+
const h = await hashPassword(longButFine, FAST_ARGON2);
|
|
53
|
+
expect(h.startsWith(ARGON2ID_PREFIX)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("counts UTF-8 bytes (not code points) when enforcing the bcrypt limit", async () => {
|
|
57
|
+
// "é" is 2 bytes in UTF-8 — 40 of them = 80 bytes, over the 72 limit.
|
|
58
|
+
const multibyte = "é".repeat(40);
|
|
59
|
+
await expect(
|
|
60
|
+
hashPassword(multibyte, { algorithm: "bcrypt", cost: 4 }),
|
|
61
|
+
).rejects.toThrow(/72-byte limit/);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("honors non-default argon2 timeCost via option pass-through", async () => {
|
|
65
|
+
// Bun embeds the cost parameters in the hash string:
|
|
66
|
+
// $argon2id$v=19$m=<memoryCost>,t=<timeCost>,p=1$<salt>$<hash>
|
|
67
|
+
// We assert the timeCost is preserved end-to-end.
|
|
68
|
+
const h = await hashPassword("same-password", {
|
|
69
|
+
algorithm: "argon2id",
|
|
70
|
+
memoryCost: 4,
|
|
71
|
+
timeCost: 3,
|
|
72
|
+
});
|
|
73
|
+
expect(h).toMatch(/\$argon2id\$v=\d+\$m=4,t=3,p=\d+\$/);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("@mandujs/core/auth/password — verifyPassword", () => {
|
|
78
|
+
it("returns true for a matching argon2id roundtrip", async () => {
|
|
79
|
+
const h = await hashPassword("correct-horse-battery-staple", FAST_ARGON2);
|
|
80
|
+
const ok = await verifyPassword("correct-horse-battery-staple", h);
|
|
81
|
+
expect(ok).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns true for a matching bcrypt roundtrip", async () => {
|
|
85
|
+
const h = await hashPassword("correct-horse-battery-staple", {
|
|
86
|
+
algorithm: "bcrypt",
|
|
87
|
+
cost: 4,
|
|
88
|
+
});
|
|
89
|
+
const ok = await verifyPassword("correct-horse-battery-staple", h);
|
|
90
|
+
expect(ok).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("returns false for wrong plaintext", async () => {
|
|
94
|
+
const h = await hashPassword("correct", FAST_ARGON2);
|
|
95
|
+
expect(await verifyPassword("incorrect", h)).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns false for a tampered hash (one char mutated)", async () => {
|
|
99
|
+
const h = await hashPassword("correct", FAST_ARGON2);
|
|
100
|
+
// Flip the last character of the base64 hash segment.
|
|
101
|
+
const last = h.charAt(h.length - 1);
|
|
102
|
+
const replacement = last === "A" ? "B" : "A";
|
|
103
|
+
const tampered = h.slice(0, -1) + replacement;
|
|
104
|
+
expect(tampered).not.toBe(h);
|
|
105
|
+
expect(await verifyPassword("correct", tampered)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns false (never throws) for a malformed hash string", async () => {
|
|
109
|
+
// Bun.password.verify throws on unparseable hashes; our wrapper collapses
|
|
110
|
+
// every failure mode to `false` so login handlers stay branchless.
|
|
111
|
+
expect(await verifyPassword("anything", "not-a-hash")).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns false for empty plaintext", async () => {
|
|
115
|
+
const h = await hashPassword("correct", FAST_ARGON2);
|
|
116
|
+
expect(await verifyPassword("", h)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("returns false for empty hash", async () => {
|
|
120
|
+
expect(await verifyPassword("correct", "")).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
});
|