Files
tech-log-frontend/tests/features/tech-log/public-query.test.ts
T

343 lines
11 KiB
TypeScript

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);
});