feat: compose executable frontend runtime

This commit is contained in:
donghyeon-ka
2026-07-25 23:42:39 +09:00
parent 9200c80149
commit 9a120e6d45
15 changed files with 505 additions and 21 deletions
+2 -1
View File
@@ -21,8 +21,9 @@ test("@a11y keyboard reaches the primary route action with visible focus", async
page,
}) => {
await page.goto("/");
await page.keyboard.press("Tab");
const action = page.getByRole("link", { name: "샘플 리소스" });
await expect(action).toBeVisible();
await page.keyboard.press("Tab");
await expect(action).toBeFocused();
await expect(action).toHaveCSS("outline-style", "solid");
});
@@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import {
loadReleaseManifest,
ReleaseManifestError,
} from "../../src/bootstrap/load-release-manifest.js";
const runtime = {
build: { buildId: "build-a" },
config: {
RELEASE_MANIFEST_URL: "/release-manifest.json",
BUILD_ID: "build-a",
RELEASE_ID: "release-a",
CONFIG_SCHEMA_VERSION: "1",
API_CONTRACT_VERSION: "1",
},
};
const manifest = {
schemaVersion: 1,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "1",
apiContractVersion: "1",
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
};
describe("release manifest boot boundary", () => {
it("loads a coherent release tuple", async () => {
await expect(
loadReleaseManifest(
/** @type {Parameters<typeof loadReleaseManifest>[0]} */ (runtime),
{ fetcher: async () => new Response(JSON.stringify(manifest)) },
),
).resolves.toMatchObject({ releaseId: "release-a" });
});
it("fails before mount when release and runtime differ", async () => {
await expect(
loadReleaseManifest(
/** @type {Parameters<typeof loadReleaseManifest>[0]} */ (runtime),
{
fetcher: async () =>
new Response(JSON.stringify({ ...manifest, buildId: "build-b" })),
},
),
).rejects.toBeInstanceOf(ReleaseManifestError);
});
});
@@ -35,6 +35,20 @@ describe("runtime configuration boundary", () => {
);
});
it("allows demo authentication only for local runtime configuration", () => {
expect(
validateRuntimeConfig({ ...validConfig, AUTH_MODE: "demo" }).success,
).toBe(true);
expect(
validateRuntimeConfig({
...validConfig,
APP_ENV: "production",
API_BASE_URL: "https://api.example.test",
AUTH_MODE: "demo",
}).success,
).toBe(false);
});
it("validates a fetched config under the 500ms budget excluding network", async () => {
let current = 100;
const result = await loadRuntimeConfig({
+23
View File
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";
import {
createAnonymousSessionAdapter,
createDemoSessionAdapter,
createExternalAuthSessionAdapter,
} from "../../src/adapters/auth/external-session-adapter.js";
@@ -9,6 +10,9 @@ describe("external AuthSessionPort adapter", () => {
it("attaches opaque credentials without exposing a token-shaped session", async () => {
const adapter = createExternalAuthSessionAdapter({
readState: () => "authenticated",
subscribe: () => () => {},
beginSignIn: async () => {},
signOut: async () => {},
attachCredential: async (request) => {
const headers = new Headers(request.headers);
headers.set("X-Session-Attached", "true");
@@ -28,6 +32,9 @@ describe("external AuthSessionPort adapter", () => {
it("fails invalid recovery states closed", async () => {
const adapter = createExternalAuthSessionAdapter({
readState: () => "authenticated",
subscribe: () => () => {},
beginSignIn: async () => {},
signOut: async () => {},
attachCredential: async (request) => request,
recoverSession: async () => "unexpected",
notifyUnauthenticated: vi.fn(),
@@ -41,4 +48,20 @@ describe("external AuthSessionPort adapter", () => {
expect(adapter.getState()).toBe("unauthenticated");
await expect(adapter.recover()).resolves.toBe("no-session");
});
it("provides a reactive credential-free demo seam", async () => {
const adapter = createDemoSessionAdapter();
let notifications = 0;
const unsubscribe = adapter.subscribe(() => {
notifications += 1;
});
expect(adapter.getState()).toBe("unauthenticated");
await adapter.beginSignIn("/");
expect(adapter.getState()).toBe("authenticated");
await adapter.signOut();
expect(adapter.getState()).toBe("unauthenticated");
expect(notifications).toBe(2);
unsubscribe();
});
});
+57
View File
@@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";
import { createRuntimeAdapters } from "../../src/bootstrap/runtime-adapters.js";
const runtime = {
config: {
APP_ENV: "local",
API_BASE_URL: "http://localhost:8080",
TELEMETRY_ENABLED: false,
AUTH_MODE: "demo",
},
};
const release = {
schemaVersion: 1,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "1",
apiContractVersion: "1",
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
};
describe("runtime adapter composition", () => {
it("constructs the local demo seam and infrastructure adapters", async () => {
const adapters = await createRuntimeAdapters({
runtime:
/** @type {Parameters<typeof createRuntimeAdapters>[0]["runtime"]} */ (
runtime
),
release,
host: {},
});
expect(adapters.authSession.getState()).toBe("unauthenticated");
expect(adapters.cache.read(["missing"])).toEqual({
ok: true,
value: undefined,
});
await expect(adapters.releaseInfo.getCurrent()).resolves.toMatchObject({
releaseId: "release-a",
});
});
it("fails closed when an external auth owner was not installed", async () => {
const adapters = await createRuntimeAdapters({
runtime:
/** @type {Parameters<typeof createRuntimeAdapters>[0]["runtime"]} */ ({
config: { ...runtime.config, AUTH_MODE: "external" },
}),
release,
host: {},
});
expect(adapters.authSession.getState()).toBe("integration-failed");
});
});