Adds TECH_LOG_STUDIO_SOURCE (MOCK | HTTP, default MOCK) to the V2 runtime
config schema so a build can switch createTechLogFeatureInstalledInput
between the mock and HTTP Studio gateways without a rebuild. V1 documents
predate the key and always normalize to MOCK. The HTTP gateway is
constructed with only { operations } per Task 4's actual signature -
no CSRF provider is wired here; that lands with attachCredentials at a
later composition-root task.
Updates every existing Studio test call site to the new required
createTechLogFeatureInstalledInput(context) signature via a shared
tests/helpers/studio-install-context.ts MOCK fixture, so the whole
existing Studio suite keeps exercising the mock adapter unchanged.
110 lines
3.9 KiB
TypeScript
110 lines
3.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "vitest";
|
|
|
|
import type { ApplicationFeatureInputs } from "../../../src/application/ports/in/application-api.ts";
|
|
import { createInstalledFeatureInputs } from "../../../src/features/installed-feature-adapters.ts";
|
|
import { FIXTURE_IDS } from "../../../src/features/tech-log/adapters/mock/fixtures.ts";
|
|
import type { TechLogFeatureInput } from "../../../src/features/tech-log/application/tech-log-feature-input.ts";
|
|
import type { WorkingCopy } from "../../../src/features/tech-log/contracts/studio/contract.ts";
|
|
|
|
type Equal<Left, Right> =
|
|
(<Value>() => Value extends Left ? 1 : 2) extends <Value>() =>
|
|
Value extends Right ? 1 : 2
|
|
? true
|
|
: false;
|
|
type Expect<Value extends true> = Value;
|
|
|
|
type InstalledInputs = ReturnType<typeof createInstalledFeatureInputs>;
|
|
type _InstalledIdsAreExact = Expect<
|
|
Equal<keyof InstalledInputs, "reference-feature" | "tech-log">
|
|
>;
|
|
type _TechLogRegistryValueIsExact = Expect<
|
|
Equal<ApplicationFeatureInputs["tech-log"], TechLogFeatureInput>
|
|
>;
|
|
void (0 as unknown as _InstalledIdsAreExact);
|
|
void (0 as unknown as _TechLogRegistryValueIsExact);
|
|
|
|
function inputOf(document: WorkingCopy) {
|
|
const { id, version, updatedAt, ...input } = document;
|
|
void id;
|
|
void version;
|
|
void updatedAt;
|
|
return input;
|
|
}
|
|
|
|
function installedInputs(
|
|
studioSource: "MOCK" | "HTTP" = "MOCK",
|
|
): InstalledInputs {
|
|
return createInstalledFeatureInputs({
|
|
studioSource,
|
|
contractOperations: {
|
|
async execute() {
|
|
throw new Error("reference executor is not used by composition tests");
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
test("installs TechLog beside the retained reference feature through application-facing inputs", () => {
|
|
const installed = installedInputs();
|
|
|
|
assert.deepEqual(Object.keys(installed), ["reference-feature", "tech-log"]);
|
|
assert.deepEqual(Object.keys(installed["tech-log"]).sort(), [
|
|
"createStudioGateway",
|
|
"publicContent",
|
|
]);
|
|
assert.equal(Object.isFrozen(installed), true);
|
|
assert.equal(Object.isFrozen(installed["tech-log"]), true);
|
|
assert.equal(Object.isFrozen(installed["tech-log"].publicContent), true);
|
|
assert.equal(
|
|
installed["tech-log"].publicContent.getRelease("0.1.0")?.title,
|
|
"TechLog Public·Studio 경계를 확정했습니다",
|
|
);
|
|
});
|
|
|
|
test("selects the mock gateway by default and the HTTP gateway when switched", async () => {
|
|
const mockGateway = installedInputs("MOCK")["tech-log"].createStudioGateway();
|
|
const httpGateway = installedInputs("HTTP")["tech-log"].createStudioGateway();
|
|
|
|
// The mock reads an in-memory fixture immediately. HTTP defers to the
|
|
// (here, throwing) contract executor, so it rejects instead.
|
|
await assert.doesNotReject(mockGateway.getDashboard());
|
|
await assert.rejects(httpGateway.getDashboard());
|
|
});
|
|
|
|
test("each createStudioGateway call owns an isolated mutable Studio session", async () => {
|
|
const installed = installedInputs();
|
|
const first = installed["tech-log"].createStudioGateway();
|
|
const second = installed["tech-log"].createStudioGateway();
|
|
|
|
assert.notEqual(first, second);
|
|
const firstBefore = await first.getDocument(FIXTURE_IDS.fetchJoinCase);
|
|
const secondBefore = await second.getDocument(FIXTURE_IDS.fetchJoinCase);
|
|
assert.equal(firstBefore.document.title, secondBefore.document.title);
|
|
|
|
const saved = await first.saveDocument(
|
|
firstBefore.document.id,
|
|
{
|
|
expectedVersion: firstBefore.document.version,
|
|
document: {
|
|
...inputOf(firstBefore.document),
|
|
title: "첫 번째 세션에서만 수정",
|
|
},
|
|
},
|
|
{ idempotencyKey: "session-isolation" },
|
|
);
|
|
|
|
assert.equal(saved.document.title, "첫 번째 세션에서만 수정");
|
|
assert.equal(
|
|
(await second.getDocument(FIXTURE_IDS.fetchJoinCase)).document.title,
|
|
secondBefore.document.title,
|
|
);
|
|
assert.equal(
|
|
installed["tech-log"].publicContent.getRecord(
|
|
"CASE",
|
|
"collection-fetch-join-pagination",
|
|
)?.title,
|
|
"컬렉션 Fetch Join과 페이징은 왜 충돌하는가",
|
|
);
|
|
});
|