feat: compose TechLog static and mock adapters
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
|
||||
import {
|
||||
caseDocument,
|
||||
caseSections,
|
||||
documentHeadings,
|
||||
focusItems,
|
||||
latestEntries,
|
||||
} from "../../../src/features/tech-log/adapters/static/content.ts";
|
||||
import {
|
||||
profile,
|
||||
projects,
|
||||
publicRecords,
|
||||
releases,
|
||||
} from "../../../src/features/tech-log/adapters/static/public-content.ts";
|
||||
import {
|
||||
getHomeFocusItems,
|
||||
getProject,
|
||||
getProjectActivity,
|
||||
getProjectDecisions,
|
||||
getProjectRecords,
|
||||
getRecord,
|
||||
getRelease,
|
||||
listRecords,
|
||||
searchPublicContent,
|
||||
} from "../../../src/features/tech-log/adapters/static/public-query.ts";
|
||||
|
||||
test("filters the canonical record set by kind, topic, project, and open-question state", () => {
|
||||
assert.deepEqual(
|
||||
listRecords({ kind: "CASE" }).map((item) => item.slug),
|
||||
["collection-fetch-join-pagination", "redis-adapter-ttl-boundary"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
listRecords({ topic: " jPa ", project: " BACKEND-SKELETON " }).map(
|
||||
(item) => item.slug,
|
||||
),
|
||||
[
|
||||
"collection-fetch-join-pagination",
|
||||
"jpa-list-fetch-strategy",
|
||||
"collection-fetch-join-with-pagination",
|
||||
],
|
||||
);
|
||||
assert.deepEqual(
|
||||
listRecords({ topic: "JPA", project: " Backend Skeleton " }).map(
|
||||
(item) => item.slug,
|
||||
),
|
||||
[
|
||||
"collection-fetch-join-pagination",
|
||||
"jpa-list-fetch-strategy",
|
||||
"collection-fetch-join-with-pagination",
|
||||
],
|
||||
);
|
||||
assert.deepEqual(listRecords({ topic: " " }), []);
|
||||
assert.deepEqual(listRecords({ project: " " }), []);
|
||||
assert.deepEqual(
|
||||
listRecords({ topic: "JPA" }).map((item) => item.slug),
|
||||
[
|
||||
"collection-fetch-join-pagination",
|
||||
"jpa-list-fetch-strategy",
|
||||
"collection-fetch-join-with-pagination",
|
||||
],
|
||||
);
|
||||
assert.deepEqual(
|
||||
listRecords({ project: "backend-skeleton" }).map((item) => item.slug),
|
||||
[
|
||||
"collection-fetch-join-pagination",
|
||||
"jpa-list-fetch-strategy",
|
||||
"redis-adapter-ttl-boundary",
|
||||
"collection-fetch-join-with-pagination",
|
||||
],
|
||||
);
|
||||
assert.deepEqual(
|
||||
listRecords({ kind: "QUESTION", openQuestionsOnly: true }).map(
|
||||
(item) => item.slug,
|
||||
),
|
||||
["validate-edge-token-again"],
|
||||
);
|
||||
});
|
||||
|
||||
test("searches public entities by title, summary, topic, and project without duplicate paths", () => {
|
||||
assert.deepEqual(
|
||||
searchPublicContent("JPA").map((item) => item.path),
|
||||
[
|
||||
"/cases/collection-fetch-join-pagination",
|
||||
"/references/jpa-list-fetch-strategy",
|
||||
"/questions/collection-fetch-join-with-pagination",
|
||||
"/projects/backend-skeleton",
|
||||
],
|
||||
);
|
||||
assert.equal(searchPublicContent("Backend Skeleton").length, 5);
|
||||
assert.deepEqual(
|
||||
searchPublicContent("Keycloak").map((item) => item.path),
|
||||
["/projects/auth-lab"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
searchPublicContent("Storage").map((item) => item.path),
|
||||
["/projects/backend-skeleton"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
searchPublicContent("요청 위조 방지").map((item) => item.path),
|
||||
["/references/state-and-nonce-boundary"],
|
||||
);
|
||||
|
||||
const allPaths = searchPublicContent("").map((item) => item.path);
|
||||
assert.equal(allPaths.length, 9);
|
||||
assert.equal(new Set(allPaths).size, allPaths.length);
|
||||
});
|
||||
|
||||
test("looks up canonical records, projects, and releases while leaving unknown identifiers absent", () => {
|
||||
assert.equal(
|
||||
getRecord("CASE", "collection-fetch-join-pagination")?.title,
|
||||
"컬렉션 Fetch Join과 페이징은 왜 충돌하는가",
|
||||
);
|
||||
assert.equal(
|
||||
getRecord("QUESTION", "validate-edge-token-again")?.questionStatus,
|
||||
"OPEN",
|
||||
);
|
||||
assert.equal(getRecord("CASE", "missing"), undefined);
|
||||
assert.equal(getProject("backend-skeleton")?.title, "Backend Skeleton");
|
||||
assert.equal(getProject("missing"), undefined);
|
||||
assert.equal(getRelease("0.1.0")?.version, "0.1.0");
|
||||
assert.equal(getRelease("9.9.9"), undefined);
|
||||
});
|
||||
|
||||
test("derives project records, decisions, and activity from canonical relationships", () => {
|
||||
assert.equal(getProjectRecords("backend-skeleton").length, 4);
|
||||
assert.deepEqual(
|
||||
getProjectDecisions("backend-skeleton").map((item) => item.id),
|
||||
["storage-port-unification", "feed-pagination-boundary"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
getProjectActivity("auth-lab").map((item) => item.id),
|
||||
["state-nonce-reference", "edge-trust-boundary"],
|
||||
);
|
||||
assert.deepEqual(getProjectRecords("missing"), []);
|
||||
assert.deepEqual(getProjectDecisions("missing"), []);
|
||||
assert.deepEqual(getProjectActivity("missing"), []);
|
||||
});
|
||||
|
||||
test("keeps the approved canonical corpus and grounded profile", () => {
|
||||
assert.equal(publicRecords.length, 6);
|
||||
assert.equal(projects.length, 2);
|
||||
assert.equal(releases.length, 1);
|
||||
assert.equal(profile.name, "동현");
|
||||
assert.equal(profile.email, undefined);
|
||||
assert.deepEqual(profile.topics, [
|
||||
"Backend Architecture",
|
||||
"JPA",
|
||||
"Authentication",
|
||||
"Redis",
|
||||
]);
|
||||
assert.deepEqual(
|
||||
publicRecords.map((item) => item.path),
|
||||
[
|
||||
"/cases/collection-fetch-join-pagination",
|
||||
"/cases/redis-adapter-ttl-boundary",
|
||||
"/references/state-and-nonce-boundary",
|
||||
"/references/jpa-list-fetch-strategy",
|
||||
"/questions/validate-edge-token-again",
|
||||
"/questions/collection-fetch-join-with-pagination",
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("preserves the existing home timeline and Fetch Join Case compatibility exactly", () => {
|
||||
assert.deepEqual(
|
||||
latestEntries.map(({ typeLabel, title, path }) => ({
|
||||
typeLabel,
|
||||
title,
|
||||
path,
|
||||
})),
|
||||
[
|
||||
{
|
||||
typeLabel: "CASE",
|
||||
title: "컬렉션 Fetch Join과 페이징은 왜 충돌하는가",
|
||||
path: "/cases/collection-fetch-join-pagination",
|
||||
},
|
||||
{
|
||||
typeLabel: "PROJECT ACTIVITY",
|
||||
title: "파일 저장소 계약을 하나로 통합했습니다",
|
||||
path: "/projects/backend-skeleton/activity#storage-contract",
|
||||
},
|
||||
{
|
||||
typeLabel: "REFERENCE",
|
||||
title: "Authorization Code Flow에서 state와 nonce의 경계",
|
||||
path: "/references/state-and-nonce-boundary",
|
||||
},
|
||||
{
|
||||
typeLabel: "PROJECT ACTIVITY",
|
||||
title: "oauth2-proxy 뒤에서 토큰을 다시 검증할 것인가",
|
||||
path: "/projects/auth-lab/activity#edge-trust-boundary",
|
||||
},
|
||||
{
|
||||
typeLabel: "CASE",
|
||||
title: "Redis Adapter가 도메인 TTL 정책을 소유하지 않는 이유",
|
||||
path: "/cases/redis-adapter-ttl-boundary",
|
||||
},
|
||||
{
|
||||
typeLabel: "RELEASE",
|
||||
title: "TechLog Public·Studio 경계를 확정했습니다",
|
||||
path: "/releases/0.1.0",
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
caseDocument.summary,
|
||||
"반환된 20건 뒤에서 전체 컬렉션이 로드되는 과정을 측정하고, 목록의 페이지 경계를 다시 세웠습니다.",
|
||||
);
|
||||
assert.equal(
|
||||
caseDocument.problem,
|
||||
"FeedItem 20건을 요청했지만 컬렉션 Fetch Join 때문에 DB LIMIT가 사라지고, 전체 부모와 자식 행을 읽은 뒤 메모리에서 20건만 남겼습니다.",
|
||||
);
|
||||
assert.equal(
|
||||
caseDocument.conclusion,
|
||||
"목록 페이징 쿼리와 컬렉션 로딩을 분리하고, 현재 페이지의 부모 키만 IN 배치로 조회합니다.",
|
||||
);
|
||||
assert.equal(
|
||||
caseDocument.dataset,
|
||||
"Dataset: FeedItem 100개, Zipf 편중 Highlight/Mention",
|
||||
);
|
||||
|
||||
const fetchJoin = getRecord("CASE", "collection-fetch-join-pagination");
|
||||
assert.ok(fetchJoin);
|
||||
assert.equal("headings" in fetchJoin, false);
|
||||
assert.deepEqual(
|
||||
documentHeadings,
|
||||
fetchJoin.sections.map(({ id, title }) => ({ id, label: title })),
|
||||
);
|
||||
assert.equal(caseSections, fetchJoin.sections);
|
||||
});
|
||||
|
||||
test("derives public home focus and Case verification labels from canonical content", () => {
|
||||
const fetchJoin = getRecord("CASE", "collection-fetch-join-pagination");
|
||||
const derivedFocusItems = getHomeFocusItems();
|
||||
|
||||
assert.equal(fetchJoin?.lastVerifiedLabel, "2026.08.11");
|
||||
assert.equal(
|
||||
caseDocument.dates,
|
||||
"게시 2026.08.11 · 마지막 검증 2026.08.11",
|
||||
);
|
||||
assert.deepEqual(derivedFocusItems, [
|
||||
{
|
||||
key: "current",
|
||||
label: "현재 작업",
|
||||
title: "Backend Skeleton",
|
||||
summary:
|
||||
"저장소·Redis·JPA·MongoDB 같은 기술을 붙일 때 애플리케이션 경계를 다시 만들지 않도록 공통 계약을 정리하는 프로젝트입니다.",
|
||||
details: [
|
||||
{ label: "단계", value: "DESIGN" },
|
||||
{
|
||||
label: "현재 목표",
|
||||
value: "Filesystem과 Object Storage를 하나의 StoragePort로 통합",
|
||||
},
|
||||
{
|
||||
label: "다음 작업",
|
||||
value: "MinIO Adapter와 공통 계약 테스트 연결",
|
||||
},
|
||||
],
|
||||
targetPath: "/projects/backend-skeleton",
|
||||
},
|
||||
{
|
||||
key: "question",
|
||||
label: "열린 질문",
|
||||
title: "oauth2-proxy가 전달한 토큰을 다시 검증해야 하는가?",
|
||||
summary:
|
||||
"Edge에서 인증한 뒤 Resource Server가 무엇을 신뢰할지 결정하기 위한 열린 질문입니다.",
|
||||
details: [
|
||||
{
|
||||
label: "확인한 사실",
|
||||
value:
|
||||
"oauth2-proxy는 Access Token 또는 사용자 식별 헤더를 upstream에 전달할 수 있습니다.",
|
||||
},
|
||||
{
|
||||
label: "남은 미지수",
|
||||
value:
|
||||
"내부 요청에서 사용자 헤더 위조를 어떤 계층이 차단할지 아직 확정하지 않았습니다.",
|
||||
},
|
||||
{
|
||||
label: "다음 검증",
|
||||
value:
|
||||
"토큰 전달안과 신뢰 헤더안을 위협 모델로 비교하고 Edge 우회 요청을 포함한 통합 테스트를 실행합니다.",
|
||||
},
|
||||
],
|
||||
targetPath: "/questions/validate-edge-token-again",
|
||||
},
|
||||
{
|
||||
key: "decision",
|
||||
label: "최근 결정",
|
||||
title:
|
||||
"Filesystem과 Object Storage는 하나의 StoragePort와 서로 다른 Adapter로 구성합니다.",
|
||||
summary:
|
||||
"애플리케이션이 요구하는 저장 의미는 같고 실제 저장 방식만 달라지므로 호출 계약을 기술별로 나누지 않습니다.",
|
||||
details: [
|
||||
{
|
||||
label: "영향",
|
||||
value: "로컬과 MinIO 구현이 같은 계약 테스트를 통과해야 합니다.",
|
||||
},
|
||||
{
|
||||
label: "근거",
|
||||
value: "파일 저장소 계약을 하나로 통합했습니다",
|
||||
},
|
||||
],
|
||||
targetPath:
|
||||
"/projects/backend-skeleton/decisions#storage-port-unification",
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(focusItems, derivedFocusItems);
|
||||
});
|
||||
|
||||
test("keeps template-specific evidence instead of reducing records to search cards", () => {
|
||||
const fetchJoin = getRecord("CASE", "collection-fetch-join-pagination");
|
||||
const stateNonce = getRecord("REFERENCE", "state-and-nonce-boundary");
|
||||
const edgeTrust = getRecord("QUESTION", "validate-edge-token-again");
|
||||
|
||||
assert.equal(fetchJoin?.kind, "CASE");
|
||||
if (fetchJoin?.kind === "CASE") {
|
||||
assert.match(fetchJoin.problem, /DB LIMIT/);
|
||||
assert.match(fetchJoin.conclusion, /IN 배치/);
|
||||
}
|
||||
|
||||
assert.equal(stateNonce?.kind, "REFERENCE");
|
||||
if (stateNonce?.kind === "REFERENCE") {
|
||||
assert.equal(stateNonce.rules.length, 3);
|
||||
assert.match(stateNonce.rules[0].body, /state/);
|
||||
}
|
||||
|
||||
assert.equal(edgeTrust?.kind, "QUESTION");
|
||||
if (edgeTrust?.kind === "QUESTION") {
|
||||
assert.equal(edgeTrust.questionStatus, "OPEN");
|
||||
assert.ok(edgeTrust.facts.length >= 2);
|
||||
assert.match(edgeTrust.nextValidation, /위협 모델/);
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
projects[0].currentGoal,
|
||||
"Filesystem과 Object Storage를 하나의 StoragePort로 통합",
|
||||
);
|
||||
assert.match(releases[0].changes[0], /Public/);
|
||||
assert.ok(profile.principles.length >= 3);
|
||||
});
|
||||
Reference in New Issue
Block a user