feat: add removable reference feature vertical slice
This commit is contained in:
@@ -7,46 +7,48 @@ import { createApplication } from "../../src/application/create-application.js";
|
||||
* preferences?: import("../../src/application/ports/storage-port.js").StoragePort,
|
||||
* diagnostics?: import("../../src/application/ports/telemetry-port.js").TelemetryPort,
|
||||
* releaseInfo?: import("../../src/application/ports/release-info-port.js").ReleaseInfoPort,
|
||||
* navigation?: { reload(): void }
|
||||
* navigation?: { reload(): void },
|
||||
* featureInputs?: Readonly<Record<string, unknown>>
|
||||
* }} [overrides]
|
||||
*/
|
||||
export function createTestApplication(overrides = {}) {
|
||||
return createApplication({
|
||||
session: overrides.session ?? createAnonymousSessionAdapter(),
|
||||
preferences:
|
||||
overrides.preferences ??
|
||||
{
|
||||
read: () => ({ ok: /** @type {const} */ (true), value: "system" }),
|
||||
write: () => ({ ok: /** @type {const} */ (true) }),
|
||||
remove: () => ({ ok: /** @type {const} */ (true) }),
|
||||
},
|
||||
diagnostics: overrides.diagnostics ?? { emit: () => {} },
|
||||
releaseInfo:
|
||||
overrides.releaseInfo ??
|
||||
{
|
||||
getCurrent: async () => ({
|
||||
buildId: "test-build",
|
||||
releaseId: "test-release",
|
||||
configSchemaVersion: "1",
|
||||
apiContractVersion: "1",
|
||||
assetManifestHash: "test-hash",
|
||||
routeChunks: {
|
||||
"route-home": "assets/home.js",
|
||||
"route-sample-resources": "assets/sample.js",
|
||||
},
|
||||
}),
|
||||
refresh: async () => ({
|
||||
buildId: "test-build",
|
||||
releaseId: "test-release",
|
||||
configSchemaVersion: "1",
|
||||
apiContractVersion: "1",
|
||||
assetManifestHash: "test-hash",
|
||||
routeChunks: {
|
||||
"route-home": "assets/home.js",
|
||||
"route-sample-resources": "assets/sample.js",
|
||||
},
|
||||
}),
|
||||
},
|
||||
navigation: overrides.navigation ?? { reload: () => {} },
|
||||
});
|
||||
return createApplication(
|
||||
{
|
||||
session: overrides.session ?? createAnonymousSessionAdapter(),
|
||||
preferences:
|
||||
overrides.preferences ??
|
||||
{
|
||||
read: () => ({ ok: /** @type {const} */ (true), value: "system" }),
|
||||
write: () => ({ ok: /** @type {const} */ (true) }),
|
||||
remove: () => ({ ok: /** @type {const} */ (true) }),
|
||||
},
|
||||
diagnostics: overrides.diagnostics ?? { emit: () => {} },
|
||||
releaseInfo:
|
||||
overrides.releaseInfo ??
|
||||
{
|
||||
getCurrent: async () => ({
|
||||
buildId: "test-build",
|
||||
releaseId: "test-release",
|
||||
configSchemaVersion: "1",
|
||||
apiContractVersion: "1",
|
||||
assetManifestHash: "test-hash",
|
||||
routeChunks: {
|
||||
"route-home": "assets/home.js",
|
||||
},
|
||||
}),
|
||||
refresh: async () => ({
|
||||
buildId: "test-build",
|
||||
releaseId: "test-release",
|
||||
configSchemaVersion: "1",
|
||||
apiContractVersion: "1",
|
||||
assetManifestHash: "test-hash",
|
||||
routeChunks: {
|
||||
"route-home": "assets/home.js",
|
||||
},
|
||||
}),
|
||||
},
|
||||
navigation: overrides.navigation ?? { reload: () => {} },
|
||||
},
|
||||
overrides.featureInputs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { createHttpClient } from "../../src/adapters/http/client.js";
|
||||
import { canonicalize } from "../../src/contracts/query-keys.js";
|
||||
|
||||
const entitySchema = z
|
||||
.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
})
|
||||
.passthrough();
|
||||
const payloadSchemas = {
|
||||
EntityListPayload: z.array(entitySchema),
|
||||
EntityPayload: entitySchema,
|
||||
};
|
||||
const requestSchemas = {
|
||||
EntityListQuery: z
|
||||
.object({
|
||||
cursor: z.string().optional(),
|
||||
limit: z.coerce.number().int().min(1).max(100).default(20),
|
||||
tags: z.array(z.string().trim().min(1)).optional(),
|
||||
})
|
||||
.strict(),
|
||||
CreateEntityCommand: z
|
||||
.object({ name: z.string().trim().min(1).max(120) })
|
||||
.strict(),
|
||||
};
|
||||
|
||||
export const TEST_OPERATIONS = Object.freeze({
|
||||
LIST_ENTITIES: Object.freeze({
|
||||
method: "GET",
|
||||
path: "/api/entities",
|
||||
operationId: "LIST_ENTITIES",
|
||||
auth: "external-session",
|
||||
timeoutMs: null,
|
||||
idempotency: "safe",
|
||||
retry: "runtime",
|
||||
requestSource: "search",
|
||||
requestSchema: "EntityListQuery",
|
||||
responseSchema: "EntityListPayload",
|
||||
owner: "test-fixture",
|
||||
}),
|
||||
CREATE_ENTITY: Object.freeze({
|
||||
method: "POST",
|
||||
path: "/api/entities",
|
||||
operationId: "CREATE_ENTITY",
|
||||
auth: "external-session",
|
||||
timeoutMs: null,
|
||||
idempotency: "keyed",
|
||||
retry: "runtime",
|
||||
requestSource: "body",
|
||||
requestSchema: "CreateEntityCommand",
|
||||
responseSchema: "EntityPayload",
|
||||
owner: "test-fixture",
|
||||
}),
|
||||
});
|
||||
|
||||
export const entityQueryKeys = Object.freeze({
|
||||
list: (filters: Readonly<Record<string, unknown>> = {}) =>
|
||||
Object.freeze(["entity", 1, canonicalize(filters)]),
|
||||
});
|
||||
|
||||
type Validation =
|
||||
| Readonly<{ success: true; data: unknown }>
|
||||
| Readonly<{ success: false }>;
|
||||
|
||||
function project(schema: z.ZodType | undefined, value: unknown): Validation {
|
||||
const result = schema?.safeParse(value);
|
||||
if (!result?.success) return { success: false };
|
||||
return { success: true, data: structuredClone(result.data) };
|
||||
}
|
||||
|
||||
type HttpDependencies = Parameters<typeof createHttpClient>[0];
|
||||
|
||||
export const TEST_HTTP_CONTRACT = Object.freeze({
|
||||
getOperation(operationId: string) {
|
||||
const operation =
|
||||
TEST_OPERATIONS[operationId as keyof typeof TEST_OPERATIONS];
|
||||
if (!operation) throw new Error(`Unknown test operation: ${operationId}`);
|
||||
return operation;
|
||||
},
|
||||
validatePayload(schemaId: string, value: unknown) {
|
||||
return project(
|
||||
payloadSchemas[schemaId as keyof typeof payloadSchemas],
|
||||
value,
|
||||
);
|
||||
},
|
||||
validateRequest(schemaId: string, value: unknown) {
|
||||
return project(
|
||||
requestSchemas[schemaId as keyof typeof requestSchemas],
|
||||
value,
|
||||
);
|
||||
},
|
||||
mapPayload(operationId: string, payload: unknown) {
|
||||
const mapOne = (value: unknown) => {
|
||||
const entity = value as { id: string; name: string };
|
||||
return { id: entity.id, displayName: entity.name };
|
||||
};
|
||||
if (operationId === "LIST_ENTITIES") {
|
||||
return (payload as readonly unknown[]).map(mapOne);
|
||||
}
|
||||
if (operationId === "CREATE_ENTITY") return mapOne(payload);
|
||||
throw new Error(`Unknown test mapper: ${operationId}`);
|
||||
},
|
||||
}) satisfies Pick<
|
||||
HttpDependencies,
|
||||
"getOperation" | "validatePayload" | "validateRequest" | "mapPayload"
|
||||
>;
|
||||
Reference in New Issue
Block a user