Files
clean-architecture-frontend…/tests/unit/realtime/fixture.ts
T
2026-08-01 19:39:59 +09:00

253 lines
7.0 KiB
TypeScript

import {
mappingSuccess,
type InstalledBoundaryMapper,
} from "../../../src/contracts/boundary-mapper.ts";
import type { ApiOperation } from "../../../src/contracts/api-operations.ts";
import {
createRealtimePolicyRegistry,
defineEventTypeId,
defineExternalEventEffectProfileId,
defineRealtimeEndpointId,
defineRealtimeKillSwitchId,
defineStreamRegistrationId,
type RealtimeEventTypeRegistration,
type RealtimeLimits,
type RealtimePolicyRegistry,
type RealtimeRecoveryProfile,
type RealtimeStreamRegistration,
} from "../../../src/contracts/realtime-streams.ts";
import type { RuntimeSchemaCodec } from "../../../src/contracts/schema-registry.ts";
import {
createRealtimeEventCodec,
type RealtimeEventCodec,
} from "../../../src/adapters/realtime/event-codec.ts";
export const STREAM_ID = defineStreamRegistrationId("REFERENCE_STREAM");
export const EVENT_TYPE = defineEventTypeId("REFERENCE_CHANGED");
export const ENDPOINT_ID = defineRealtimeEndpointId("REFERENCE_ENDPOINT");
export const EFFECT_PROFILE_ID =
defineExternalEventEffectProfileId("REFERENCE_INVALIDATE");
export const KILL_SWITCH_ID =
defineRealtimeKillSwitchId("REFERENCE_KILL_SWITCH");
export const TEST_LIMITS: RealtimeLimits = Object.freeze({
maxEventBytes: 4_096,
maxPayloadDepth: 8,
maxPayloadNodes: 128,
maxQueueEvents: 8,
maxQueueBytes: 32_768,
maxDedupeEntries: 16,
maxDedupeBytes: 32_768,
dedupeTtlMs: 60_000,
});
const eventPayloadCodec: RuntimeSchemaCodec = Object.freeze({
schemaId: "ReferenceRealtimePayload",
parse(value) {
if (
!value ||
typeof value !== "object" ||
Array.isArray(value) ||
Object.keys(value).length !== 1 ||
typeof (value as Readonly<Record<string, unknown>>).value !== "string"
) {
return {
success: false,
issues: [{ path: "value", code: "INVALID_TYPE" }],
};
}
return {
success: true,
data: {
value: (value as Readonly<Record<string, string>>).value,
},
};
},
});
const checkpointCodec: RuntimeSchemaCodec = Object.freeze({
schemaId: "ReferenceRealtimeCheckpoint",
parse: (value) => ({ success: true, data: value }),
});
export const TEST_SCHEMA_CODECS = Object.freeze({
ReferenceRealtimePayload: eventPayloadCodec,
ReferenceRealtimeCheckpoint: checkpointCodec,
});
export const TEST_MAPPER: InstalledBoundaryMapper = Object.freeze({
mapperId: "ReferenceRealtimeMapper",
mapperVersion: 1,
inputSchemaId: "ReferenceRealtimePayload",
outputContractId: "ReferenceRealtimeEvent",
owner: "sample-owner",
maxOutputItems: 1,
map(input) {
if (
!input ||
typeof input !== "object" ||
typeof (input as Readonly<Record<string, unknown>>).value !== "string"
) {
return { ok: false, code: "MAPPING_INVARIANT_REJECTED" };
}
return mappingSuccess(
Object.freeze({
value: (input as Readonly<Record<string, string>>).value,
}),
);
},
});
export const TEST_MAPPERS = Object.freeze({
ReferenceRealtimeMapper: TEST_MAPPER,
});
const snapshotOperation: ApiOperation = Object.freeze({
method: "GET",
path: "/api/reference-snapshot",
operationId: "GET_REFERENCE_REALTIME_SNAPSHOT",
auth: "external-session",
timeoutMs: 10_000,
idempotency: "safe",
retry: "never",
requestSource: "none",
requestSchema: "NoRequest",
responseSchema: "ReferenceRealtimeCheckpoint",
owner: "sample-owner",
contractVersion: 2,
protocol: "REST",
semantics: "QUERY",
replayPolicy: "SAFE",
idempotencyKeyPolicy: "NONE",
mapperId: "ReferenceRealtimeSnapshotMapper",
successStatuses: [200],
responseMediaTypes: ["application/json"],
maxResponseBytes: 16_384,
providerId: "PRIMARY_API",
authProfileId: "EXTERNAL_SESSION",
csrfProfileId: "NONE",
pathSchema: "NoRequest",
pathParameterNames: [],
maxEncodedSearchBytes: 0,
});
export const TEST_API_OPERATIONS = Object.freeze({
GET_REFERENCE_REALTIME_SNAPSHOT: snapshotOperation,
});
export type TestRegistryOptions = Readonly<{
recovery?: RealtimeRecoveryProfile;
delivery?: RealtimeStreamRegistration["delivery"];
stateBearing?: boolean;
limits?: RealtimeLimits;
streamMutator?: (
stream: RealtimeStreamRegistration,
) => RealtimeStreamRegistration;
eventTypeMutator?: (
eventType: RealtimeEventTypeRegistration,
) => RealtimeEventTypeRegistration;
}>;
export function createTestRealtimeRegistry(
options: TestRegistryOptions = {},
): RealtimePolicyRegistry {
const recovery =
options.recovery ??
({
mode: "CURSOR",
snapshotOperationId: "GET_REFERENCE_REALTIME_SNAPSHOT",
checkpointCodecId: "ReferenceRealtimeCheckpoint",
barrier: "REPLAY",
} as const);
const eventType: RealtimeEventTypeRegistration = {
id: EVENT_TYPE,
owner: "sample-owner",
payloadSchemaId: "ReferenceRealtimePayload",
mapperId: "ReferenceRealtimeMapper",
effectProfileId: EFFECT_PROFILE_ID,
stateBearing: options.stateBearing ?? true,
};
const stream: RealtimeStreamRegistration = {
id: STREAM_ID,
protocol: "REALTIME_EVENT_V1",
owner: "sample-owner",
scope: "ACCOUNT_BOUND",
primaryTransport: "SSE",
endpointId: ENDPOINT_ID,
eventTypeIds: [EVENT_TYPE],
delivery: options.delivery ?? "AUTHORITATIVE_DELTA",
recovery,
fallback:
recovery.mode === "SESSION_REBUILD"
? "EXPLICITLY_STALE"
: "BOUNDED_POLLING",
hiddenPolicy: "CLOSE",
limits: options.limits ?? TEST_LIMITS,
killSwitchId: KILL_SWITCH_ID,
};
return createRealtimePolicyRegistry({
streams: [options.streamMutator?.(stream) ?? stream],
eventTypes: [
options.eventTypeMutator?.(eventType) ?? eventType,
],
bindings: {
schemaCodecs: TEST_SCHEMA_CODECS,
mappers: TEST_MAPPERS,
apiOperations: TEST_API_OPERATIONS,
endpointIds: [ENDPOINT_ID],
effectProfileIds: [EFFECT_PROFILE_ID],
killSwitchIds: [KILL_SWITCH_ID],
rebuildInputIds: ["referenceRealtimeRebuild"],
},
});
}
export function createTestRealtimeCodec(
registry = createTestRealtimeRegistry(),
): RealtimeEventCodec {
return createRealtimeEventCodec({
registry,
schemaCodecs: TEST_SCHEMA_CODECS,
});
}
export type EventOverrides = Readonly<{
protocol?: unknown;
streamId?: unknown;
streamEpoch?: unknown;
eventType?: unknown;
eventId?: unknown;
sequence?: unknown;
recoveryMode?: unknown;
resumeCursor?: unknown;
occurredAt?: unknown;
scopeBinding?: unknown;
payload?: unknown;
}>;
export function realtimeEventValue(
overrides: EventOverrides = {},
): Readonly<Record<string, unknown>> {
return {
protocol: "REALTIME_EVENT_V1",
streamId: STREAM_ID,
streamEpoch: "stream-epoch-0001",
eventType: EVENT_TYPE,
eventId: "event-00000001",
sequence: "1",
recoveryMode: "CURSOR",
resumeCursor: "cursor-00000001",
occurredAt: "2026-07-28T01:02:03.123Z",
scopeBinding: "scope-binding-0001",
payload: { value: "changed" },
...overrides,
};
}
export function realtimeEventJson(
overrides: EventOverrides = {},
): string {
return JSON.stringify(realtimeEventValue(overrides));
}