Files
clean-architecture-frontend…/tests/unit/service-worker-web-push-remediation.test.ts
T
DongHyeonkaandClaude Opus 5 efc577de63 fix: bound Service Worker marker reads and attribute native effects
SW-RR-01. The activation marker was read with response.text() whenever no
Content-Length was present, so a large or non-terminating body could consume
the whole activation step. It now reads through a bounded reader that stops one
byte past the ceiling, cancels its reader, applies a read deadline and decodes
UTF-8 fatally.

SW-RR-02. A matching nonce is not identity. An activation or reset result whose
event.source is null can no longer stand in for the expected worker; only a
strict identity match is admitted.

SW-RR-03. The build generator and the shared manifest decoder now read one
exported extension table, so .mjs and .png stop being emitted-then-refused.
.json is deliberately outside it: every JSON file in a build output is a control
document the generator already excludes, not a cacheable asset.

SW-RR-04. Both caches.open and cache.match are closed as a miss. Letting a
match rejection propagate rejected respondWith itself, so the entry never
reached its network fallback.

WP-RR-01. focus and openWindow now carry the certainty phase showNotification
already had — NOT_APPLIED, MAYBE_APPLIED, CONFIRMED — and an effect that lands
after the handler deadline is observed exactly once. The evidence never
authorizes a retry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:10:41 +09:00

70 lines
2.0 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
CACHEABLE_ASSET_CONTENT_TYPES,
decodeStaticAssetManifest,
} from "../../src/contracts/service-worker-static-manifest.ts";
/**
* SW-RR-03. The build generator and the shared runtime decoder must agree on
* exactly which asset kinds exist. A generator that emits `.mjs` or `.png` while
* the decoder refuses them turns a correct build into a runtime contract
* failure, and the reverse admits a kind no build produces.
*/
describe("SW-RR-03 one authoritative cacheable asset table", () => {
it("covers every extension the generator emits", async () => {
const generator = await import(
"../../scripts/generate-service-worker-assets.ts"
);
expect(generator).toBeDefined();
for (const extension of [
".js",
".mjs",
".css",
".woff2",
".svg",
".png",
".webp",
]) {
expect(CACHEABLE_ASSET_CONTENT_TYPES[extension]).toBeDefined();
}
});
it("decodes a manifest row for every table entry", () => {
const assets = Object.entries(CACHEABLE_ASSET_CONTENT_TYPES).map(
([extension, contentType], index) => ({
url: `/assets/name-abcdefgh${index}${extension}`,
sha256: `sha256:${"a".repeat(64)}`,
bytes: 16,
contentType,
}),
);
const decoded = decodeStaticAssetManifest({
schemaVersion: 1,
buildId: "build-1",
releaseId: "release-1",
assets,
setDigest: `sha256:${"b".repeat(64)}`,
});
expect(decoded).toMatchObject({ ok: true });
});
it("refuses a row whose extension is not in the table", () => {
const decoded = decodeStaticAssetManifest({
schemaVersion: 1,
buildId: "build-1",
releaseId: "release-1",
setDigest: `sha256:${"b".repeat(64)}`,
assets: [
{
url: "/assets/control-abcdefgh.json",
sha256: `sha256:${"a".repeat(64)}`,
bytes: 16,
contentType: "application/json",
},
],
});
expect(decoded.ok).toBe(false);
});
});