Files
tech-log-frontend/tests/unit/browser-rpc/browser-rpc-contract.test.ts
T
DongHyeonkaandClaude Opus 5 4bff9ca151 chore: sync the frontend template from 4dc033c to 8157ad4
The product was materialized from the template at `4dc033c` and has stayed
on it through 43 template commits, so it was missing all three rounds of
adapter remediation — including files it never had, such as the shared
`abortable-operation` primitive and the `exact-snapshot` decoder that
later fixes are written against. Taking only the newest round was not
possible for that reason: the delta is coherent only as a whole.

The product had not touched `src/adapters` at all since materialization,
so the 140-file delta applied with a three-way merge and no conflicts.
`package.json` was the single overlap and merged cleanly: the product owns
`name`, the template contributed `check:adapter-inventory`,
`check:remediation-ledger` and the image-resolve-signal type fixture.
All 24 product-owned files — README, index.html, CI workflow, i18n
catalog, home page, generated schemas, evidence scripts, component and
visual snapshots — are byte-identical to `main`.

`template.lock.json` now pins the synced revision and tree.

Verified in this repository, not inherited from the template: six type
projects, lint, nine gates (adapter inventory, remediation ledger,
registries, diagnostics, realtime boundaries, architecture, browser
file/storage boundaries, optional recipes, documentation), the production
build, and 2,054 of 2,073 tests. The 19 failures are all in
`tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing
sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template
records; four suites that failed once under parallel load pass in
isolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 12:04:58 +09:00

351 lines
11 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
composeBrowserRpcOperationRegistry,
composeBrowserRpcProviderProfileRegistry,
composeBrowserRpcRequestEncoderRegistry,
defineBrowserRpcOperation,
defineBrowserRpcProviderProfile,
installBrowserRpcContractBindings,
validateBrowserRpcContractBindings,
type BrowserRpcProviderProfile,
} from "../../../src/contracts/browser-rpc.ts";
import {
DESCRIPTOR_DIGEST,
MAPPERS,
RUNTIME_DIGEST,
SCHEMA_CODECS,
STREAM_ENCODER,
UNARY_ENCODER,
streamOperation,
streamProfile,
unaryOperation,
unaryProfile,
} from "./fixture.ts";
describe("Browser RPC contract registry", () => {
it("snapshots installed bindings before later source mutation", () => {
const operations: Record<string, ReturnType<typeof unaryOperation>> = {
GET_RPC_RESOURCE: unaryOperation(),
};
const installed = installBrowserRpcContractBindings({
operations,
profiles: { [unaryProfile().runtimeProfileId]: unaryProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: { RpcResourceRequestEncoder: UNARY_ENCODER },
});
const before = installed.operations.get("GET_RPC_RESOURCE");
expect(before?.totalDeadlineMs).toBeDefined();
// R-04. A post-validation mutation of the source registry must not reach
// the installed snapshot.
operations.GET_RPC_RESOURCE = {
...operations.GET_RPC_RESOURCE!,
totalDeadlineMs: 999_999,
};
expect(installed.operations.get("GET_RPC_RESOURCE")).toBe(before);
expect(
installed.operations.get("GET_RPC_RESOURCE")?.totalDeadlineMs,
).not.toBe(999_999);
});
it("rejects extra accessor and symbol keys without invoking getters", () => {
let getterCalls = 0;
const accessorOperation = Object.defineProperty(
{ ...unaryOperation() },
"totalDeadlineMs",
{
enumerable: true,
configurable: true,
get() {
getterCalls += 1;
return 1_000;
},
},
);
expect(() =>
installBrowserRpcContractBindings({
operations: { GET_RPC_RESOURCE: accessorOperation },
profiles: { [unaryProfile().runtimeProfileId]: unaryProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: { RpcResourceRequestEncoder: UNARY_ENCODER },
}),
).toThrow(TypeError);
expect(getterCalls).toBe(0);
const extraKeyOperation = {
...unaryOperation(),
unexpectedKey: "smuggled",
};
expect(() =>
installBrowserRpcContractBindings({
operations: {
GET_RPC_RESOURCE: extraKeyOperation as never,
},
profiles: { [unaryProfile().runtimeProfileId]: unaryProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: { RpcResourceRequestEncoder: UNARY_ENCODER },
}),
).toThrow(/unexpected key/u);
const symbolRegistry: Record<string, unknown> = {
GET_RPC_RESOURCE: unaryOperation(),
};
Object.defineProperty(symbolRegistry, Symbol("hidden"), {
enumerable: true,
value: unaryOperation(),
});
expect(() =>
installBrowserRpcContractBindings({
operations: symbolRegistry as never,
profiles: { [unaryProfile().runtimeProfileId]: unaryProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: { RpcResourceRequestEncoder: UNARY_ENCODER },
}),
).toThrow(/symbol keys/u);
});
it("closes exact operation, provider, schema, mapper and encoder bindings", () => {
const operation = unaryOperation();
const profile = unaryProfile();
const operations = composeBrowserRpcOperationRegistry([
{ GET_RPC_RESOURCE: operation },
]);
const profiles = composeBrowserRpcProviderProfileRegistry([
{ CONNECT_REFERENCE_UNARY: profile },
]);
const encoders = composeBrowserRpcRequestEncoderRegistry([
{ RpcResourceRequestEncoder: UNARY_ENCODER },
]);
expect(
validateBrowserRpcContractBindings({
operations,
profiles,
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: encoders,
}),
).toBe(true);
expect(Object.isFrozen(operations)).toBe(true);
expect(Object.isFrozen(profiles.CONNECT_REFERENCE_UNARY)).toBe(true);
expect(
Object.isFrozen(
profiles.CONNECT_REFERENCE_UNARY?.allowedProcedures,
),
).toBe(true);
});
it("rejects duplicate rows and descriptor/provider drift", () => {
const operation = unaryOperation();
expect(() =>
composeBrowserRpcOperationRegistry([
{ GET_RPC_RESOURCE: operation },
{ GET_RPC_RESOURCE: operation },
]),
).toThrow("duplicate Browser RPC operation");
expect(() =>
validateBrowserRpcContractBindings({
operations: { GET_RPC_RESOURCE: operation },
profiles: {
CONNECT_REFERENCE_UNARY: unaryProfile({
descriptorDigest: "c".repeat(64),
}),
},
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("provider binding is invalid");
});
it("permits only a public headerless NO_SIDE_EFFECTS Connect GET", () => {
const getProfile = defineBrowserRpcProviderProfile({
...unaryProfile(),
runtimeProfileId: "CONNECT_PUBLIC_GET",
requestMethod: "GET",
authProfileId: "ANONYMOUS",
csrfProfileId: "NONE",
allowedProcedures: [
"example.resource.v1.ResourceService/GetResource",
],
});
const validGet = defineBrowserRpcOperation({
...unaryOperation(),
runtimeProfileId: "CONNECT_PUBLIC_GET",
authProfileId: "ANONYMOUS",
csrfProfileId: "NONE",
idempotencyLevel: "NO_SIDE_EFFECTS",
dataClassification: "PUBLIC",
});
expect(
validateBrowserRpcContractBindings({
operations: { GET_RPC_RESOURCE: validGet },
profiles: { CONNECT_PUBLIC_GET: getProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toBe(true);
expect(() =>
validateBrowserRpcContractBindings({
operations: {
GET_RPC_RESOURCE: defineBrowserRpcOperation({
...validGet,
dataClassification: "CONFIDENTIAL",
}),
},
profiles: { CONNECT_PUBLIC_GET: getProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("GET binding is invalid");
});
it("separates official grpc-web, Connect-Web and Connect tuples", () => {
expect(() =>
defineBrowserRpcProviderProfile({
...streamProfile(),
runtimeProfileId: "OFFICIAL_BINARY_STREAM",
runtimeId: "official-grpc-web",
runtimeVersion: "1.5.0",
runtimeDigest: RUNTIME_DIGEST,
protocol: "GRPC_WEB",
runtimeKind: "OFFICIAL_GRPC_WEB_XHR",
clientApiKind: "CALLBACK_STREAM",
messageEncoding: "PROTO",
framing: "GRPC_WEB_BINARY_ENVELOPE",
deadlineDialect: "OFFICIAL_DEADLINE_METADATA",
cancelDialect: "CLIENT_READABLE_STREAM_CANCEL",
descriptorDigest: DESCRIPTOR_DIGEST,
}),
).toThrow("provider profile is invalid");
expect(() =>
defineBrowserRpcProviderProfile({
...unaryProfile(),
framing: "GRPC_WEB_BINARY_ENVELOPE",
}),
).toThrow("provider profile is invalid");
expect(
defineBrowserRpcProviderProfile({
...unaryProfile(),
runtimeProfileId: "CONNECT_GRPC_WEB_UNARY",
protocol: "GRPC_WEB",
framing: "GRPC_WEB_BINARY_ENVELOPE",
deadlineDialect: "GRPC_TIMEOUT",
}),
).toMatchObject({
protocol: "GRPC_WEB",
runtimeKind: "CONNECT_WEB_FETCH",
deadlineDialect: "GRPC_TIMEOUT",
});
});
it("revalidates raw registry rows instead of trusting TypeScript assertions", () => {
const invalidProfile = {
...unaryProfile(),
messageEncoding: "XML",
} as unknown as BrowserRpcProviderProfile;
expect(() =>
validateBrowserRpcContractBindings({
operations: { GET_RPC_RESOURCE: unaryOperation() },
profiles: { CONNECT_REFERENCE_UNARY: invalidProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("provider profile is invalid");
expect(() =>
validateBrowserRpcContractBindings({
operations: { WRONG_REGISTRY_KEY: unaryOperation() },
profiles: { CONNECT_REFERENCE_UNARY: unaryProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("operation registry is invalid");
});
it("disallows frontend stream retry and unsafe unary replay", () => {
expect(() =>
defineBrowserRpcProviderProfile({
...streamProfile(),
retryOwner: "FRONTEND_ADAPTER",
maxAttempts: 2,
backoffMs: [10],
retryableFailures: ["UNAVAILABLE"],
}),
).toThrow("provider profile is invalid");
const retryProfile = unaryProfile({
retryProfileId: "RPC_RETRY_TWO",
retryOwner: "FRONTEND_ADAPTER",
maxAttempts: 2,
backoffMs: [10],
retryableFailures: ["UNAVAILABLE"],
});
expect(() =>
validateBrowserRpcContractBindings({
operations: {
CREATE_RPC_RESOURCE: defineBrowserRpcOperation({
...unaryOperation(),
operationId: "CREATE_RPC_RESOURCE",
semantics: "COMMAND",
replayPolicy: "NON_REPLAYABLE",
runtimeProfileId: retryProfile.runtimeProfileId,
retryProfileId: retryProfile.retryProfileId,
}),
},
profiles: { CONNECT_REFERENCE_UNARY: retryProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: {
...UNARY_ENCODER,
operationId: "CREATE_RPC_RESOURCE",
},
},
}),
).toThrow("retry binding is invalid");
});
it("supports a bounded Connect server-stream contract without composing it", () => {
expect(
validateBrowserRpcContractBindings({
operations: {
WATCH_RPC_RESOURCES: streamOperation(),
},
profiles: {
CONNECT_REFERENCE_STREAM: streamProfile(),
},
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceStreamRequestEncoder: STREAM_ENCODER,
},
}),
).toBe(true);
});
});