1409 lines
41 KiB
TypeScript
1409 lines
41 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import type {
|
|
BackendIssuedImageAsset,
|
|
ImageCapabilityVerifier,
|
|
ImagePresetReference,
|
|
ImageProbeRequest,
|
|
PublicImmutableImageAsset,
|
|
} from "../../src/application/ports/browser-transfer/image-cdn.ts";
|
|
import {
|
|
IMAGE_CDN_IMPLEMENTATION_CEILINGS,
|
|
ImageCdnPolicyRegistry,
|
|
imageCdnPresetReference,
|
|
type ImageCdnHardLimits,
|
|
type ImageCdnPresetPolicy,
|
|
} from "../../src/adapters/browser-transfer/image-cdn/image-cdn-policy.ts";
|
|
import {
|
|
computeImageCapabilityBindingDigestHex,
|
|
createImageCdnRuntime,
|
|
DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
|
|
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
|
|
type ImageCapabilityVerificationScheduler,
|
|
} from "../../src/adapters/browser-transfer/image-cdn/image-cdn-runtime.ts";
|
|
|
|
const hardLimits: ImageCdnHardLimits = Object.freeze({
|
|
maxIntrinsicWidth: 4_096,
|
|
maxIntrinsicHeight: 4_096,
|
|
maxSourcePixels: 16_777_216,
|
|
maxCssDimension: 2_048,
|
|
maxDpr: 2,
|
|
maxQuality: 90,
|
|
maxCandidateCount: 8,
|
|
maxTransformedPixels: 1_048_576,
|
|
maxDecodedBytes: 4_194_304,
|
|
maxEncodedBytes: 524_288,
|
|
maxUrlLength: 2_048,
|
|
maxCapabilityLifetimeMs: 3_600_000,
|
|
maxClockSkewMs: 60_000,
|
|
minCapabilityRemainingMs: 30_000,
|
|
maxPresetBindingsPerCapability: 8,
|
|
maxConcurrentCapabilityVerifications: 8,
|
|
allowedSourceMediaTypes: [
|
|
"image/avif",
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/webp",
|
|
] as const,
|
|
formatQualityCeilings: {
|
|
avif: 80,
|
|
jpeg: 85,
|
|
png: 90,
|
|
webp: 85,
|
|
},
|
|
});
|
|
|
|
function acceptsTestImageKey(keyId: string): boolean {
|
|
return keyId === "image-signing-2026-01";
|
|
}
|
|
|
|
function createPreset(
|
|
reference: ImagePresetReference,
|
|
overrides: Partial<ImageCdnPresetPolicy> = {},
|
|
): ImageCdnPresetPolicy {
|
|
return {
|
|
reference,
|
|
bindingId: "card-landscape-v1",
|
|
width: 640,
|
|
height: 360,
|
|
fit: "cover",
|
|
dprs: [2, 1],
|
|
responsiveWidths: [640, 320],
|
|
quality: 80,
|
|
formats: ["avif", "webp", "jpeg"],
|
|
sizes: "(max-width: 640px) 100vw, 640px",
|
|
loading: "eager",
|
|
decoding: "async",
|
|
fetchPriority: "high",
|
|
referrerPolicy: "strict-origin-when-cross-origin",
|
|
probeMode: "NONE",
|
|
allowUpscale: false,
|
|
maxTransformedPixels: 1_048_576,
|
|
maxDecodedBytes: 4_194_304,
|
|
maxEncodedBytes: 524_288,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createPolicies(
|
|
presets: readonly ImageCdnPresetPolicy[],
|
|
overrides: Readonly<{
|
|
hardLimits?: ImageCdnHardLimits;
|
|
acceptedKeyIds?: readonly string[];
|
|
}> = {},
|
|
): ImageCdnPolicyRegistry {
|
|
return new ImageCdnPolicyRegistry({
|
|
applicationOrigin: "https://app.example.test",
|
|
origins: [
|
|
{
|
|
originKey: "product-images",
|
|
origin: "https://images.example.test",
|
|
assetPathPrefix: "/v1/assets/",
|
|
minimumPublicMaxAgeSeconds: 31_536_000,
|
|
},
|
|
],
|
|
presets,
|
|
hardLimits: overrides.hardLimits ?? hardLimits,
|
|
capability: {
|
|
issuer: "image-bff",
|
|
acceptedKeyIds: overrides.acceptedKeyIds ?? [
|
|
"image-signing-2026-01",
|
|
],
|
|
},
|
|
});
|
|
}
|
|
|
|
function publicAsset(
|
|
overrides: Partial<PublicImmutableImageAsset> = {},
|
|
): PublicImmutableImageAsset {
|
|
return {
|
|
kind: "ALLOWLISTED_PUBLIC",
|
|
originKey: "product-images",
|
|
assetId: "asset_Q3x8pL",
|
|
revision: "rev_a8N2kP4z",
|
|
mediaType: "image/jpeg",
|
|
contentKind: "RASTER_STATIC",
|
|
intrinsicWidth: 2_048,
|
|
intrinsicHeight: 1_152,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
async function privateAsset(
|
|
overrides: Partial<BackendIssuedImageAsset> = {},
|
|
): Promise<BackendIssuedImageAsset> {
|
|
const base: BackendIssuedImageAsset = {
|
|
kind: "BACKEND_ISSUED_PRIVATE",
|
|
issuer: "image-bff",
|
|
originKey: "product-images",
|
|
assetId: "asset_Q3x8pL",
|
|
revision: "rev_a8N2kP4z",
|
|
mediaType: "image/jpeg",
|
|
contentKind: "RASTER_STATIC",
|
|
intrinsicWidth: 2_048,
|
|
intrinsicHeight: 1_152,
|
|
capabilityId: "cap_Z8m2Q7pR",
|
|
issuedAtEpochMs: 1_000_000,
|
|
expiresAtEpochMs: 1_300_000,
|
|
allowedPresetBindingIds: ["card-landscape-v1"],
|
|
signature: {
|
|
algorithm: "ECDSA_P256_SHA256",
|
|
keyId: "image-signing-2026-01",
|
|
capabilityBindingDigestHex: "0".repeat(64),
|
|
valueBase64Url: "A".repeat(86),
|
|
},
|
|
...overrides,
|
|
};
|
|
const descriptor = {
|
|
...base,
|
|
signature: {
|
|
...base.signature,
|
|
...(overrides.signature ?? {}),
|
|
},
|
|
};
|
|
const digest = await computeImageCapabilityBindingDigestHex(
|
|
globalThis.crypto.subtle,
|
|
descriptor,
|
|
);
|
|
return {
|
|
...descriptor,
|
|
signature: {
|
|
...descriptor.signature,
|
|
capabilityBindingDigestHex: digest,
|
|
},
|
|
};
|
|
}
|
|
|
|
import { manualCapabilityVerificationScheduler } from "./image-cdn-test-fixture.ts";
|
|
|
|
describe("production image CDN runtime", () => {
|
|
it("builds sorted, duplicate-free responsive candidates from a named preset", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"card-landscape",
|
|
"render-public-product-image",
|
|
);
|
|
const runtime = createImageCdnRuntime({
|
|
policies: createPolicies([createPreset(preset)]),
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: vi.fn().mockResolvedValue(true),
|
|
},
|
|
});
|
|
const accepted = runtime.assets.acceptPublicImmutable(
|
|
publicAsset(),
|
|
);
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
|
|
const resolved = await runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(resolved.ok).toBe(true);
|
|
if (!resolved.ok) return;
|
|
|
|
const widthDescriptors = resolved.value.srcSet
|
|
.split(", ")
|
|
.map((candidate) =>
|
|
Number(candidate.match(/ (\d+)w$/u)?.[1]),
|
|
);
|
|
expect(widthDescriptors).toEqual([320, 640, 1_280]);
|
|
expect(new Set(widthDescriptors).size).toBe(3);
|
|
expect(resolved.value.sources.map((source) => source.type)).toEqual(
|
|
["image/avif", "image/webp"],
|
|
);
|
|
expect(resolved.value.fallbackMediaType).toBe("image/jpeg");
|
|
expect(resolved.value).toMatchObject({
|
|
width: 640,
|
|
height: 360,
|
|
loading: "eager",
|
|
decoding: "async",
|
|
fetchPriority: "high",
|
|
crossOrigin: "anonymous",
|
|
delivery: {
|
|
class: "PUBLIC_IMMUTABLE",
|
|
browserCache: "PUBLIC_IMMUTABLE",
|
|
sharedCache: "PUBLIC_IMMUTABLE",
|
|
purge: "REVISION_ROLLOVER",
|
|
expiresAtEpochMs: null,
|
|
},
|
|
decodeBudget: {
|
|
maximumCandidatePixels: 921_600,
|
|
maximumDecodedBytes: 3_686_400,
|
|
},
|
|
});
|
|
|
|
const primary = new URL(resolved.value.src);
|
|
expect(primary.origin).toBe("https://images.example.test");
|
|
expect(primary.pathname).toBe(
|
|
"/v1/assets/asset_Q3x8pL/rev_a8N2kP4z",
|
|
);
|
|
expect([...primary.searchParams.keys()]).toEqual([
|
|
"dpr",
|
|
"fit",
|
|
"format",
|
|
"height",
|
|
"preset",
|
|
"quality",
|
|
"width",
|
|
]);
|
|
expect(Object.fromEntries(primary.searchParams)).toEqual({
|
|
dpr: "1",
|
|
fit: "cover",
|
|
format: "jpeg",
|
|
height: "360",
|
|
preset: "card-landscape-v1",
|
|
quality: "80",
|
|
width: "640",
|
|
});
|
|
});
|
|
|
|
it("rejects raw URL/query/transform input and forged preset or asset references", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"card-landscape",
|
|
"render-public-product-image",
|
|
);
|
|
const runtime = createImageCdnRuntime({
|
|
policies: createPolicies([createPreset(preset)]),
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: vi.fn().mockResolvedValue(true),
|
|
},
|
|
});
|
|
|
|
expect(
|
|
runtime.assets.acceptPublicImmutable({
|
|
...publicAsset(),
|
|
sourceUrl: `${"java"}script:alert(1)`,
|
|
} as PublicImmutableImageAsset),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INVALID_INPUT" },
|
|
});
|
|
expect(
|
|
runtime.assets.acceptPublicImmutable(
|
|
publicAsset({
|
|
assetId: "../../admin?width=9999",
|
|
}),
|
|
),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
expect(
|
|
runtime.assets.acceptPublicImmutable(
|
|
publicAsset({
|
|
mediaType: "image/svg+xml",
|
|
} as unknown as Partial<PublicImmutableImageAsset>),
|
|
),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
|
|
const accepted = runtime.assets.acceptPublicImmutable(
|
|
publicAsset(),
|
|
);
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
width: 9_999,
|
|
query: "format=svg",
|
|
src: "data:text/html,active",
|
|
} as unknown as Parameters<typeof runtime.presentation.resolve>[0]),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "INVALID_INPUT" },
|
|
});
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset: imageCdnPresetReference(
|
|
"card-landscape",
|
|
"render-public-product-image",
|
|
),
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: {} as typeof accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
});
|
|
|
|
it("deep-snapshots origin and preset policy against TOCTOU mutation", async () => {
|
|
const presetReference = imageCdnPresetReference(
|
|
"card-landscape",
|
|
"render-public-product-image",
|
|
);
|
|
const origin = {
|
|
originKey: "product-images",
|
|
origin: "https://images.example.test",
|
|
assetPathPrefix: "/v1/assets/",
|
|
minimumPublicMaxAgeSeconds: 31_536_000,
|
|
};
|
|
const preset = createPreset(presetReference);
|
|
const policies = new ImageCdnPolicyRegistry({
|
|
applicationOrigin: "https://app.example.test",
|
|
origins: [origin],
|
|
presets: [preset],
|
|
hardLimits,
|
|
capability: {
|
|
issuer: "image-bff",
|
|
acceptedKeyIds: ["image-signing-2026-01"],
|
|
},
|
|
});
|
|
origin.origin = "https://attacker.invalid";
|
|
origin.assetPathPrefix = "/stolen/";
|
|
(preset.dprs as number[])[0] = 4;
|
|
(preset.formats as string[])[0] = "svg";
|
|
(preset as { quality: number }).quality = 100;
|
|
|
|
const runtime = createImageCdnRuntime({ policies });
|
|
const accepted = runtime.assets.acceptPublicImmutable(
|
|
publicAsset(),
|
|
);
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
const resolved = await runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset: presetReference,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(resolved.ok).toBe(true);
|
|
if (!resolved.ok) return;
|
|
const url = new URL(resolved.value.src);
|
|
expect(url.origin).toBe("https://images.example.test");
|
|
expect(url.pathname.startsWith("/v1/assets/")).toBe(true);
|
|
expect(url.searchParams.get("quality")).toBe("80");
|
|
expect(resolved.value.sources[0]?.type).toBe("image/avif");
|
|
});
|
|
|
|
it("rejects unsafe origins and preset budgets at composition time", () => {
|
|
const preset = imageCdnPresetReference(
|
|
"unsafe",
|
|
"reject-policy",
|
|
);
|
|
expect(
|
|
() =>
|
|
new ImageCdnPolicyRegistry({
|
|
applicationOrigin: "https://app.example.test",
|
|
origins: [
|
|
{
|
|
originKey: "bad",
|
|
origin: "http://user:password@example.test/?q=x#x",
|
|
assetPathPrefix: "/../assets/",
|
|
minimumPublicMaxAgeSeconds: 1,
|
|
},
|
|
],
|
|
presets: [createPreset(preset)],
|
|
hardLimits,
|
|
capability: {
|
|
issuer: "image-bff",
|
|
acceptedKeyIds: ["image-signing-2026-01"],
|
|
},
|
|
}),
|
|
).toThrow(/origin policy/u);
|
|
expect(
|
|
() =>
|
|
createPolicies([
|
|
createPreset(preset, {
|
|
maxTransformedPixels: 100,
|
|
maxDecodedBytes: 400,
|
|
}),
|
|
]),
|
|
).toThrow(/pixel budget/u);
|
|
|
|
const validRegistryOptions = {
|
|
origins: [
|
|
{
|
|
originKey: "product-images",
|
|
origin: "https://images.example.test",
|
|
assetPathPrefix: "/v1/assets/",
|
|
minimumPublicMaxAgeSeconds: 31_536_000,
|
|
},
|
|
],
|
|
presets: [createPreset(preset)],
|
|
hardLimits,
|
|
capability: {
|
|
issuer: "image-bff",
|
|
acceptedKeyIds: ["image-signing-2026-01"],
|
|
},
|
|
};
|
|
expect(
|
|
() =>
|
|
new ImageCdnPolicyRegistry({
|
|
...validRegistryOptions,
|
|
applicationOrigin: "https://images.example.test",
|
|
}),
|
|
).toThrow(/cross-origin/u);
|
|
for (const applicationOrigin of [
|
|
"http://app.example.test",
|
|
"https://app.example.test/",
|
|
]) {
|
|
expect(
|
|
() =>
|
|
new ImageCdnPolicyRegistry({
|
|
...validRegistryOptions,
|
|
applicationOrigin,
|
|
}),
|
|
).toThrow(/application origin/u);
|
|
}
|
|
});
|
|
|
|
it("enforces immutable implementation ceilings on every resource-bearing hard limit", () => {
|
|
const preset = imageCdnPresetReference(
|
|
"absolute-ceilings",
|
|
"bound-image-composition",
|
|
);
|
|
const ceiling = IMAGE_CDN_IMPLEMENTATION_CEILINGS;
|
|
const overCeiling: readonly Partial<ImageCdnHardLimits>[] = [
|
|
{ maxIntrinsicWidth: ceiling.maxIntrinsicWidth + 1 },
|
|
{ maxIntrinsicHeight: ceiling.maxIntrinsicHeight + 1 },
|
|
{ maxSourcePixels: ceiling.maxSourcePixels + 1 },
|
|
{ maxCssDimension: ceiling.maxCssDimension + 1 },
|
|
{ maxDpr: ceiling.maxDpr + 1 },
|
|
{ maxQuality: ceiling.maxQuality + 1 },
|
|
{ maxCandidateCount: ceiling.maxCandidateCount + 1 },
|
|
{
|
|
maxTransformedPixels:
|
|
ceiling.maxTransformedPixels + 1,
|
|
},
|
|
{ maxDecodedBytes: ceiling.maxDecodedBytes + 1 },
|
|
{ maxEncodedBytes: ceiling.maxEncodedBytes + 1 },
|
|
{ maxUrlLength: ceiling.maxUrlLength + 1 },
|
|
{
|
|
maxCapabilityLifetimeMs:
|
|
ceiling.maxCapabilityLifetimeMs + 1,
|
|
},
|
|
{ maxClockSkewMs: ceiling.maxClockSkewMs + 1 },
|
|
{
|
|
minCapabilityRemainingMs:
|
|
ceiling.maxMinimumCapabilityRemainingMs + 1,
|
|
},
|
|
{
|
|
maxPresetBindingsPerCapability:
|
|
ceiling.maxPresetBindingsPerCapability + 1,
|
|
},
|
|
{
|
|
maxConcurrentCapabilityVerifications:
|
|
ceiling.maxConcurrentCapabilityVerifications + 1,
|
|
},
|
|
];
|
|
for (const override of overCeiling) {
|
|
expect(() =>
|
|
createPolicies([createPreset(preset)], {
|
|
hardLimits: { ...hardLimits, ...override },
|
|
}),
|
|
).toThrow(/hard limits/u);
|
|
}
|
|
|
|
const boundaryLimits: ImageCdnHardLimits = {
|
|
...hardLimits,
|
|
maxIntrinsicWidth: ceiling.maxIntrinsicWidth,
|
|
maxIntrinsicHeight: ceiling.maxIntrinsicHeight,
|
|
maxSourcePixels: ceiling.maxSourcePixels,
|
|
maxCssDimension: ceiling.maxCssDimension,
|
|
maxDpr: ceiling.maxDpr,
|
|
maxQuality: ceiling.maxQuality,
|
|
maxCandidateCount: ceiling.maxCandidateCount,
|
|
maxTransformedPixels: ceiling.maxTransformedPixels,
|
|
maxDecodedBytes: ceiling.maxDecodedBytes,
|
|
maxEncodedBytes: ceiling.maxEncodedBytes,
|
|
maxUrlLength: ceiling.maxUrlLength,
|
|
maxCapabilityLifetimeMs:
|
|
ceiling.maxCapabilityLifetimeMs,
|
|
maxClockSkewMs: ceiling.maxClockSkewMs,
|
|
minCapabilityRemainingMs:
|
|
ceiling.maxMinimumCapabilityRemainingMs,
|
|
maxPresetBindingsPerCapability:
|
|
ceiling.maxPresetBindingsPerCapability,
|
|
maxConcurrentCapabilityVerifications:
|
|
ceiling.maxConcurrentCapabilityVerifications,
|
|
};
|
|
expect(() =>
|
|
createPolicies([createPreset(preset)], {
|
|
hardLimits: boundaryLimits,
|
|
}),
|
|
).not.toThrow();
|
|
});
|
|
|
|
it("snapshots a bounded unique capability-key overlap set", () => {
|
|
const preset = imageCdnPresetReference(
|
|
"key-policy",
|
|
"rotate-image-signing-key",
|
|
);
|
|
for (const acceptedKeyIds of [
|
|
[],
|
|
["image-key-current", "image-key-current"],
|
|
Array.from(
|
|
{
|
|
length:
|
|
IMAGE_CDN_IMPLEMENTATION_CEILINGS.maxAcceptedKeyIds +
|
|
1,
|
|
},
|
|
(_, index) => `image-key-${index}`,
|
|
),
|
|
["image-key-current", "invalid/key"],
|
|
]) {
|
|
expect(() =>
|
|
createPolicies([createPreset(preset)], {
|
|
acceptedKeyIds,
|
|
}),
|
|
).toThrow(/capability policy/u);
|
|
}
|
|
|
|
const mutableKeyIds = [
|
|
"image-signing-2026-02",
|
|
"image-signing-2026-01",
|
|
];
|
|
const policies = createPolicies([createPreset(preset)], {
|
|
acceptedKeyIds: mutableKeyIds,
|
|
});
|
|
mutableKeyIds[0] = "image-signing-attacker";
|
|
expect(policies.capabilityPolicy().acceptedKeyIds).toEqual([
|
|
"image-signing-2026-02",
|
|
"image-signing-2026-01",
|
|
]);
|
|
expect(
|
|
Object.isFrozen(
|
|
policies.capabilityPolicy().acceptedKeyIds,
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("accepts old and new overlap keys only when policy and verifier registries both cover them", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"key-overlap",
|
|
"verify-image-key-rotation",
|
|
);
|
|
const acceptedKeyIds = [
|
|
"image-signing-2026-02",
|
|
"image-signing-2026-01",
|
|
] as const;
|
|
const policies = createPolicies([createPreset(preset)], {
|
|
acceptedKeyIds,
|
|
});
|
|
const verifierKeys = new Set<string>(acceptedKeyIds);
|
|
const verify = vi.fn(
|
|
async (request: { keyId: string }) =>
|
|
verifierKeys.has(request.keyId),
|
|
);
|
|
const runtime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: (keyId) => verifierKeys.has(keyId),
|
|
verify,
|
|
},
|
|
});
|
|
const previous = await privateAsset();
|
|
const current = await privateAsset({
|
|
signature: {
|
|
algorithm: "ECDSA_P256_SHA256",
|
|
keyId: "image-signing-2026-02",
|
|
capabilityBindingDigestHex: "0".repeat(64),
|
|
valueBase64Url: "A".repeat(86),
|
|
},
|
|
});
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued(previous),
|
|
).resolves.toMatchObject({ ok: true });
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued(current),
|
|
).resolves.toMatchObject({ ok: true });
|
|
expect(verify.mock.calls.map(([request]) => request.keyId)).toEqual(
|
|
["image-signing-2026-01", "image-signing-2026-02"],
|
|
);
|
|
|
|
const unknown = await privateAsset({
|
|
signature: {
|
|
algorithm: "ECDSA_P256_SHA256",
|
|
keyId: "image-signing-2027-01",
|
|
capabilityBindingDigestHex: "0".repeat(64),
|
|
valueBase64Url: "A".repeat(86),
|
|
},
|
|
});
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued(unknown),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
expect(verify).toHaveBeenCalledTimes(2);
|
|
|
|
expect(() =>
|
|
createImageCdnRuntime({
|
|
policies,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: (keyId) =>
|
|
keyId === "image-signing-2026-02",
|
|
verify: vi.fn().mockResolvedValue(true),
|
|
},
|
|
}),
|
|
).toThrow(/registry does not cover/u);
|
|
expect(() =>
|
|
createImageCdnRuntime({
|
|
policies,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey() {
|
|
throw new TypeError("registry unavailable");
|
|
},
|
|
verify: vi.fn().mockResolvedValue(true),
|
|
},
|
|
}),
|
|
).toThrow(/registry does not cover/u);
|
|
});
|
|
|
|
it("binds a private descriptor to its signed snapshot and exact preset", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"card-landscape",
|
|
"render-private-product-image",
|
|
);
|
|
const policies = createPolicies([
|
|
createPreset(preset, {
|
|
probeMode: "PRIMARY_REQUIRED",
|
|
}),
|
|
]);
|
|
let releaseVerification:
|
|
((value: boolean) => void) | undefined;
|
|
const verifierMethod = vi.fn(
|
|
() =>
|
|
new Promise<boolean>((resolve) => {
|
|
releaseVerification = resolve;
|
|
}),
|
|
);
|
|
const verifier: ImageCapabilityVerifier = {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: verifierMethod,
|
|
};
|
|
let now = 1_000_000;
|
|
const runtime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => now,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: verifier,
|
|
probe: {
|
|
async probe(request) {
|
|
return {
|
|
ok: true,
|
|
value: {
|
|
absoluteUrl: request.absoluteUrl,
|
|
mediaType: request.expectedMediaType,
|
|
encodedBytes: 1_024,
|
|
decodedWidth: request.expectedWidth,
|
|
decodedHeight: request.expectedHeight,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
});
|
|
(
|
|
verifier as {
|
|
verify: ImageCapabilityVerifier["verify"];
|
|
}
|
|
).verify = vi.fn().mockResolvedValue(false);
|
|
const mutable = (await privateAsset()) as {
|
|
-readonly [Key in keyof BackendIssuedImageAsset]:
|
|
BackendIssuedImageAsset[Key];
|
|
};
|
|
const pending = runtime.assets.acceptBackendIssued(mutable);
|
|
await vi.waitFor(() => {
|
|
expect(verifierMethod).toHaveBeenCalledOnce();
|
|
});
|
|
mutable.assetId = "asset_ATTACKER";
|
|
mutable.revision = "rev_ATTACKER";
|
|
mutable.expiresAtEpochMs = 9_999_999;
|
|
releaseVerification?.(true);
|
|
const accepted = await pending;
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
|
|
const resolved = await runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(resolved.ok).toBe(true);
|
|
if (!resolved.ok) return;
|
|
const url = new URL(resolved.value.src);
|
|
expect(url.pathname).toContain(
|
|
"/asset_Q3x8pL/rev_a8N2kP4z",
|
|
);
|
|
expect(url.pathname).not.toContain("ATTACKER");
|
|
expect([...url.searchParams.keys()]).toEqual([
|
|
"binding",
|
|
"capability",
|
|
"dpr",
|
|
"expires",
|
|
"fit",
|
|
"format",
|
|
"height",
|
|
"key",
|
|
"preset",
|
|
"quality",
|
|
"signature",
|
|
"width",
|
|
]);
|
|
expect(resolved.value).toMatchObject({
|
|
loading: "eager",
|
|
referrerPolicy: "no-referrer",
|
|
crossOrigin: "anonymous",
|
|
delivery: {
|
|
class: "PRIVATE_SIGNED",
|
|
browserCache: "NO_STORE",
|
|
sharedCache: "FORBIDDEN",
|
|
purge: "CAPABILITY_REVOCATION_OR_EXPIRY",
|
|
expiresAtEpochMs: 1_300_000,
|
|
},
|
|
});
|
|
|
|
now = 1_275_001;
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "EXPIRED_RESOURCE" },
|
|
});
|
|
});
|
|
|
|
it("rejects invalid binding/signature and private lazy or low-priority presets", async () => {
|
|
const eager = imageCdnPresetReference(
|
|
"private-eager",
|
|
"render-private",
|
|
);
|
|
const lazy = imageCdnPresetReference(
|
|
"private-lazy",
|
|
"render-private",
|
|
);
|
|
const policies = createPolicies([
|
|
createPreset(eager, {
|
|
bindingId: "private-eager-v1",
|
|
}),
|
|
createPreset(lazy, {
|
|
bindingId: "private-lazy-v1",
|
|
loading: "lazy",
|
|
fetchPriority: "low",
|
|
}),
|
|
]);
|
|
const runtime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: vi.fn().mockResolvedValue(true),
|
|
},
|
|
});
|
|
const issued = await privateAsset({
|
|
allowedPresetBindingIds: [
|
|
"private-eager-v1",
|
|
"private-lazy-v1",
|
|
],
|
|
});
|
|
const accepted = await runtime.assets.acceptBackendIssued(issued);
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset: eager,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset: lazy,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued({
|
|
...issued,
|
|
signature: {
|
|
...issued.signature,
|
|
capabilityBindingDigestHex: "f".repeat(64),
|
|
},
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTEGRITY_FAILED" },
|
|
});
|
|
|
|
const rejectingRuntime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: vi.fn().mockResolvedValue(false),
|
|
},
|
|
});
|
|
await expect(
|
|
rejectingRuntime.assets.acceptBackendIssued(issued),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTEGRITY_FAILED" },
|
|
});
|
|
});
|
|
|
|
it("bounds digest and capability verification with a runtime-owned deadline and caller abort", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"deadline",
|
|
"verify-private-capability",
|
|
);
|
|
const policies = createPolicies([createPreset(preset)]);
|
|
const issued = await privateAsset();
|
|
const observations: Record<string, unknown>[] = [];
|
|
|
|
const verifierDeadline =
|
|
manualCapabilityVerificationScheduler();
|
|
const stalledVerifier = vi.fn(
|
|
() => new Promise<boolean>(() => undefined),
|
|
);
|
|
const verifierRuntime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: stalledVerifier,
|
|
},
|
|
capabilityVerificationScheduler:
|
|
verifierDeadline.scheduler,
|
|
observer: {
|
|
record(observation) {
|
|
observations.push(observation);
|
|
},
|
|
},
|
|
});
|
|
const verifierPending =
|
|
verifierRuntime.assets.acceptBackendIssued(issued);
|
|
await vi.waitFor(() => {
|
|
expect(stalledVerifier).toHaveBeenCalledOnce();
|
|
});
|
|
expect(verifierDeadline.delays).toEqual([
|
|
DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
|
|
]);
|
|
verifierDeadline.fire();
|
|
await expect(verifierPending).resolves.toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "UNAVAILABLE",
|
|
retryable: true,
|
|
recovery: "RETRY",
|
|
},
|
|
});
|
|
expect(verifierDeadline.clearTimeout).toHaveBeenCalledOnce();
|
|
expect(observations).toEqual([
|
|
expect.objectContaining({
|
|
operation: "IMAGE_RESOLVE",
|
|
outcome: "FAILED",
|
|
failureCode: "UNAVAILABLE",
|
|
countBucket: "ZERO",
|
|
}),
|
|
]);
|
|
|
|
const digestDeadline =
|
|
manualCapabilityVerificationScheduler();
|
|
const stalledDigest = vi.fn(
|
|
() => new Promise<ArrayBuffer>(() => undefined),
|
|
);
|
|
const digestVerifier = vi.fn().mockResolvedValue(true);
|
|
const digestRuntime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => 1_000_000,
|
|
subtle: {
|
|
digest: stalledDigest as SubtleCrypto["digest"],
|
|
},
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: digestVerifier,
|
|
},
|
|
capabilityVerificationTimeoutMs:
|
|
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
|
|
capabilityVerificationScheduler: digestDeadline.scheduler,
|
|
});
|
|
const digestPending =
|
|
digestRuntime.assets.acceptBackendIssued(issued);
|
|
await vi.waitFor(() => {
|
|
expect(stalledDigest).toHaveBeenCalledOnce();
|
|
});
|
|
expect(digestDeadline.delays).toEqual([
|
|
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
|
|
]);
|
|
digestDeadline.fire();
|
|
await expect(digestPending).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE" },
|
|
});
|
|
expect(digestVerifier).not.toHaveBeenCalled();
|
|
|
|
const abortDeadline =
|
|
manualCapabilityVerificationScheduler();
|
|
const abortVerifier = vi.fn(
|
|
() => new Promise<boolean>(() => undefined),
|
|
);
|
|
const abortRuntime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: abortVerifier,
|
|
},
|
|
capabilityVerificationScheduler: abortDeadline.scheduler,
|
|
});
|
|
const controller = new AbortController();
|
|
const abortPending = abortRuntime.assets.acceptBackendIssued(
|
|
issued,
|
|
{ signal: controller.signal },
|
|
);
|
|
await vi.waitFor(() => {
|
|
expect(abortVerifier).toHaveBeenCalledOnce();
|
|
});
|
|
controller.abort();
|
|
await expect(abortPending).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "ABORTED" },
|
|
});
|
|
expect(abortDeadline.clearTimeout).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
/**
|
|
* TR-RR-07. The concurrency cap exists to bound *physical* verification work.
|
|
* Releasing the slot when the wrapper's abort resolved let an abandoned
|
|
* verifier keep running while a new one was admitted, so repeated aborts
|
|
* produced more concurrent work than the configured cap allows.
|
|
*/
|
|
it("holds the verification slot until the raw verifier settles", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"verification-concurrency",
|
|
"bound-image-verification-concurrency",
|
|
);
|
|
const policies = createPolicies([createPreset(preset)], {
|
|
hardLimits: {
|
|
...hardLimits,
|
|
maxConcurrentCapabilityVerifications: 1,
|
|
},
|
|
});
|
|
let verificationAttempt = 0;
|
|
let releaseFirst: ((value: boolean) => void) | undefined;
|
|
const verify = vi.fn(() => {
|
|
verificationAttempt += 1;
|
|
return verificationAttempt === 1
|
|
? new Promise<boolean>((resolve) => {
|
|
releaseFirst = resolve;
|
|
})
|
|
: Promise.resolve(true);
|
|
});
|
|
const runtime = createImageCdnRuntime({
|
|
policies,
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify,
|
|
},
|
|
});
|
|
const issued = await privateAsset();
|
|
const controller = new AbortController();
|
|
const first = runtime.assets.acceptBackendIssued(issued, {
|
|
signal: controller.signal,
|
|
});
|
|
await vi.waitFor(() => {
|
|
expect(verify).toHaveBeenCalledOnce();
|
|
});
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued(issued),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "LIMIT_EXCEEDED" },
|
|
});
|
|
expect(verify).toHaveBeenCalledOnce();
|
|
|
|
controller.abort();
|
|
await expect(first).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "ABORTED" },
|
|
});
|
|
|
|
// The caller's wait ended, but the raw verifier has not. Admitting a second
|
|
// one here would put two physical verifications under a cap of one.
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued(issued),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "LIMIT_EXCEEDED" },
|
|
});
|
|
expect(verify).toHaveBeenCalledOnce();
|
|
|
|
// Once the raw verifier settles the slot is free again.
|
|
releaseFirst?.(true);
|
|
await vi.waitFor(async () => {
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued(issued),
|
|
).resolves.toMatchObject({ ok: true });
|
|
});
|
|
expect(verify).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("fails fast on invalid capability verification deadline composition", () => {
|
|
const preset = imageCdnPresetReference(
|
|
"deadline-config",
|
|
"validate-private-capability-deadline",
|
|
);
|
|
const policies = createPolicies([createPreset(preset)]);
|
|
expect(
|
|
DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
|
|
).toBe(5_000);
|
|
expect(MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS).toBe(
|
|
60_000,
|
|
);
|
|
for (const capabilityVerificationTimeoutMs of [
|
|
0,
|
|
1.5,
|
|
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS + 1,
|
|
]) {
|
|
expect(() =>
|
|
createImageCdnRuntime({
|
|
policies,
|
|
capabilityVerificationTimeoutMs,
|
|
}),
|
|
).toThrow(/verification timeout/u);
|
|
}
|
|
expect(() =>
|
|
createImageCdnRuntime({
|
|
policies,
|
|
capabilityVerificationScheduler: {
|
|
setTimeout: null,
|
|
clearTimeout: vi.fn(),
|
|
} as unknown as ImageCapabilityVerificationScheduler,
|
|
}),
|
|
).toThrow(/verification scheduler/u);
|
|
});
|
|
|
|
it("closes terminally and idempotently, revoking old references and rejecting every later operation", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"lifecycle",
|
|
"close-image-runtime",
|
|
);
|
|
const runtime = createImageCdnRuntime({
|
|
policies: createPolicies([createPreset(preset)]),
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: vi.fn().mockResolvedValue(true),
|
|
},
|
|
});
|
|
const accepted =
|
|
runtime.assets.acceptPublicImmutable(publicAsset());
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({ ok: true });
|
|
const acceptedPrivate =
|
|
await runtime.assets.acceptBackendIssued(
|
|
await privateAsset(),
|
|
);
|
|
expect(acceptedPrivate.ok).toBe(true);
|
|
if (!acceptedPrivate.ok) return;
|
|
|
|
runtime.close();
|
|
expect(() => runtime.close()).not.toThrow();
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "UNAVAILABLE",
|
|
retryable: false,
|
|
recovery: "NONE",
|
|
},
|
|
});
|
|
await expect(
|
|
runtime.presentation.resolve({
|
|
asset: acceptedPrivate.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE" },
|
|
});
|
|
expect(
|
|
runtime.assets.acceptPublicImmutable(publicAsset()),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE" },
|
|
});
|
|
await expect(
|
|
runtime.assets.acceptBackendIssued(await privateAsset()),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE" },
|
|
});
|
|
});
|
|
|
|
it("close aborts stalled private verification and image probing", async () => {
|
|
const verifierPreset = imageCdnPresetReference(
|
|
"close-verifier",
|
|
"abort-image-verification",
|
|
);
|
|
const verifierDeadline =
|
|
manualCapabilityVerificationScheduler();
|
|
const stalledVerifier = vi.fn(
|
|
() => new Promise<boolean>(() => undefined),
|
|
);
|
|
const verifierRuntime = createImageCdnRuntime({
|
|
policies: createPolicies([
|
|
createPreset(verifierPreset),
|
|
]),
|
|
now: () => 1_000_000,
|
|
subtle: globalThis.crypto.subtle,
|
|
capabilityVerifier: {
|
|
acceptsKey: acceptsTestImageKey,
|
|
verify: stalledVerifier,
|
|
},
|
|
capabilityVerificationScheduler:
|
|
verifierDeadline.scheduler,
|
|
});
|
|
const verificationPending =
|
|
verifierRuntime.assets.acceptBackendIssued(
|
|
await privateAsset(),
|
|
);
|
|
await vi.waitFor(() => {
|
|
expect(stalledVerifier).toHaveBeenCalledOnce();
|
|
});
|
|
verifierRuntime.close();
|
|
await expect(verificationPending).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE", retryable: false },
|
|
});
|
|
expect(verifierDeadline.clearTimeout).toHaveBeenCalledOnce();
|
|
|
|
const probePreset = imageCdnPresetReference(
|
|
"close-probe",
|
|
"abort-image-probe",
|
|
);
|
|
const stalledProbe = vi.fn(
|
|
(_request: ImageProbeRequest) =>
|
|
new Promise<never>(() => undefined),
|
|
);
|
|
const probeRuntime = createImageCdnRuntime({
|
|
policies: createPolicies([
|
|
createPreset(probePreset, {
|
|
probeMode: "PRIMARY_REQUIRED",
|
|
}),
|
|
]),
|
|
probe: { probe: stalledProbe },
|
|
});
|
|
const accepted =
|
|
probeRuntime.assets.acceptPublicImmutable(publicAsset());
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
const caller = new AbortController();
|
|
const probePending = probeRuntime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset: probePreset,
|
|
signal: caller.signal,
|
|
});
|
|
await vi.waitFor(() => {
|
|
expect(stalledProbe).toHaveBeenCalledOnce();
|
|
});
|
|
const runtimeSignal =
|
|
stalledProbe.mock.calls[0]?.[0].signal;
|
|
expect(runtimeSignal).not.toBe(caller.signal);
|
|
probeRuntime.close();
|
|
await expect(probePending).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE", retryable: false },
|
|
});
|
|
expect(runtimeSignal?.aborted).toBe(true);
|
|
});
|
|
|
|
it("requires and validates the bounded probe receipt when the preset says so", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"probed",
|
|
"render-verified-image",
|
|
);
|
|
const policy = createPreset(preset, {
|
|
bindingId: "probed-v1",
|
|
probeMode: "PRIMARY_REQUIRED",
|
|
});
|
|
const withoutProbe = createImageCdnRuntime({
|
|
policies: createPolicies([policy]),
|
|
});
|
|
const noProbeAsset =
|
|
withoutProbe.assets.acceptPublicImmutable(publicAsset());
|
|
expect(noProbeAsset.ok).toBe(true);
|
|
if (!noProbeAsset.ok) return;
|
|
await expect(
|
|
withoutProbe.presentation.resolve({
|
|
asset: noProbeAsset.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNSUPPORTED" },
|
|
});
|
|
|
|
const probe = vi.fn(async (request: ImageProbeRequest) => ({
|
|
ok: true as const,
|
|
value: {
|
|
absoluteUrl: request.absoluteUrl,
|
|
mediaType: request.expectedMediaType,
|
|
encodedBytes: 1_024,
|
|
decodedWidth: request.expectedWidth,
|
|
decodedHeight: request.expectedHeight,
|
|
},
|
|
}));
|
|
const runtime = createImageCdnRuntime({
|
|
policies: createPolicies([policy]),
|
|
probe: { probe },
|
|
});
|
|
const accepted =
|
|
runtime.assets.acceptPublicImmutable(publicAsset());
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
const resolved = await runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(resolved.ok).toBe(true);
|
|
expect(probe).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
expectedMediaType: "image/jpeg",
|
|
expectedWidth: 640,
|
|
expectedHeight: 360,
|
|
maxEncodedBytes: 524_288,
|
|
maxDecodedBytes: 4_194_304,
|
|
delivery: "PUBLIC_IMMUTABLE",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("records only safe terminal buckets for accept, probe and resolve", async () => {
|
|
const preset = imageCdnPresetReference(
|
|
"observed",
|
|
"render-observed-image",
|
|
);
|
|
const observations: Record<string, unknown>[] = [];
|
|
const observer = {
|
|
record(observation: Record<string, unknown>) {
|
|
observations.push(observation);
|
|
},
|
|
};
|
|
const runtime = createImageCdnRuntime({
|
|
policies: createPolicies([
|
|
createPreset(preset, {
|
|
bindingId: "observed-v1",
|
|
probeMode: "PRIMARY_REQUIRED",
|
|
}),
|
|
]),
|
|
observer,
|
|
probe: {
|
|
async probe(request) {
|
|
return {
|
|
ok: true,
|
|
value: {
|
|
absoluteUrl: request.absoluteUrl,
|
|
mediaType: request.expectedMediaType,
|
|
encodedBytes: 2_048,
|
|
decodedWidth: request.expectedWidth,
|
|
decodedHeight: request.expectedHeight,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
});
|
|
observer.record = () => {
|
|
throw new TypeError("mutated observer must not be observed");
|
|
};
|
|
const accepted =
|
|
runtime.assets.acceptPublicImmutable(publicAsset());
|
|
expect(accepted.ok).toBe(true);
|
|
if (!accepted.ok) return;
|
|
const resolved = await runtime.presentation.resolve({
|
|
asset: accepted.value,
|
|
preset,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(resolved.ok).toBe(true);
|
|
expect(observations).toHaveLength(3);
|
|
for (const observation of observations) {
|
|
expect(observation).toMatchObject({
|
|
operation: "IMAGE_RESOLVE",
|
|
outcome: "SUCCEEDED",
|
|
});
|
|
expect(Object.keys(observation)).not.toEqual(
|
|
expect.arrayContaining([
|
|
"assetId",
|
|
"preset",
|
|
"capability",
|
|
"signature",
|
|
"url",
|
|
]),
|
|
);
|
|
expect(JSON.stringify(observation)).not.toContain(
|
|
"images.example.test",
|
|
);
|
|
}
|
|
expect(observations.map((value) => value.countBucket)).toEqual([
|
|
"ONE",
|
|
"ONE",
|
|
"TWO_TO_TEN",
|
|
]);
|
|
expect(observations.map((value) => value.byteBucket)).toEqual([
|
|
undefined,
|
|
"LT1MIB",
|
|
"LT1MIB",
|
|
]);
|
|
});
|
|
});
|