233 lines
7.3 KiB
TypeScript
233 lines
7.3 KiB
TypeScript
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
|
|
import { createSqliteRepository } from "../sqlite-repository";
|
|
|
|
describe("createSqliteRepository", () => {
|
|
let tempDir: string;
|
|
let dbPath: string;
|
|
|
|
beforeEach(() => {
|
|
tempDir = mkdtempSync(join(tmpdir(), "geo-repository-"));
|
|
dbPath = join(tempDir, "app.db");
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("creates and reads an article job through the async repository interface", async () => {
|
|
const repository = createSqliteRepository(dbPath);
|
|
|
|
const job = await repository.createArticleJob({
|
|
source_title: "Title",
|
|
source_body: "Body",
|
|
image_inputs: [],
|
|
publish_platform: "official_site",
|
|
user_instructions: "",
|
|
});
|
|
|
|
await expect(repository.getArticleJob(job.id)).resolves.toMatchObject({
|
|
id: job.id,
|
|
source_title: "Title",
|
|
export_paths: {},
|
|
});
|
|
});
|
|
|
|
test("persists scoring, publication, performance, and calibration event", async () => {
|
|
const repository = createSqliteRepository(dbPath);
|
|
const job = await repository.createArticleJob({
|
|
source_title: "Title",
|
|
source_body: "Body",
|
|
image_inputs: [],
|
|
publish_platform: "official_site",
|
|
user_instructions: "",
|
|
});
|
|
const article = await repository.saveOptimizedArticle(job.id, {
|
|
title: "Optimized",
|
|
summary: "Summary",
|
|
body_markdown: "Body",
|
|
image_suggestions: [],
|
|
changed_sections: [],
|
|
requires_user_confirmation: [],
|
|
});
|
|
|
|
await repository.saveRubricVersion({
|
|
id: "rubric_geo_v1",
|
|
version: "v1",
|
|
name: "GEO rubric",
|
|
dimensions: [],
|
|
formula: "weighted_average_0_to_10",
|
|
is_active: true,
|
|
created_at: "2026-06-24T00:00:00.000Z",
|
|
});
|
|
const scoringRun = await repository.saveScoringRun({
|
|
id: "score_1",
|
|
job_id: job.id,
|
|
revision: article.revision ?? 1,
|
|
rubric_version_id: "rubric_geo_v1",
|
|
dimension_scores: { readability: 4 },
|
|
composite_score: 8,
|
|
rationale: "Readable",
|
|
created_at: "2026-06-24T00:00:00.000Z",
|
|
});
|
|
const publication = await repository.createPublicationRecord({
|
|
job_id: job.id,
|
|
revision: article.revision ?? 1,
|
|
platform: "official_site",
|
|
url: "https://example.com/article",
|
|
published_at: "2026-06-24T12:00:00.000Z",
|
|
status: "published",
|
|
notes: "官网首发",
|
|
});
|
|
const snapshot = await repository.savePerformanceSnapshot({
|
|
id: "perf_1",
|
|
publication_id: publication.id,
|
|
source: "manual",
|
|
window_label: "T+7d",
|
|
metrics: { views: 1200 },
|
|
feedback_summary: "用户追问案例依据",
|
|
snapshot_at: "2026-07-01T12:00:00.000Z",
|
|
});
|
|
const event = await repository.saveCalibrationEvent({
|
|
id: "cal_1",
|
|
publication_id: publication.id,
|
|
scoring_run_id: scoringRun.id,
|
|
performance_snapshot_id: snapshot.id,
|
|
direction: "better_than_expected",
|
|
observations: ["表现高于预期"],
|
|
recommended_action: "继续积累样本",
|
|
created_at: "2026-07-01T12:10:00.000Z",
|
|
});
|
|
|
|
await expect(repository.listPublicationRecords(job.id)).resolves.toHaveLength(1);
|
|
await expect(repository.getLatestScoringRun(job.id, article.revision ?? 1))
|
|
.resolves.toMatchObject({ id: scoringRun.id, composite_score: 8 });
|
|
await expect(repository.listPerformanceSnapshots(publication.id)).resolves.toEqual([
|
|
expect.objectContaining({ id: snapshot.id }),
|
|
]);
|
|
expect(event.observations).toEqual(["表现高于预期"]);
|
|
});
|
|
|
|
test("creates, lists, updates, archives, and restores optimization cases", async () => {
|
|
const repository = createSqliteRepository(dbPath);
|
|
|
|
const created = await repository.createOptimizationCase({
|
|
case_type: "human_copy",
|
|
title: "人味文案优化:朋友圈",
|
|
summary: "原文摘要",
|
|
publish_target: "朋友圈",
|
|
source_excerpt: "原文摘要",
|
|
});
|
|
|
|
await repository.saveCaseInput({
|
|
case_id: created.id,
|
|
case_type: "human_copy",
|
|
article_job_id: null,
|
|
payload: {
|
|
source_text: "原文摘要",
|
|
goal: "自然一点",
|
|
intensity: "light",
|
|
user_instructions: "",
|
|
publish_target: "朋友圈",
|
|
},
|
|
});
|
|
|
|
await expect(
|
|
repository.listOptimizationCases({ include_archived: false }),
|
|
).resolves.toEqual([expect.objectContaining({ id: created.id })]);
|
|
|
|
await expect(
|
|
repository.updateOptimizationCaseMetadata(created.id, {
|
|
customer_name: "客户A",
|
|
brand_name: "品牌B",
|
|
project_tags: ["朋友圈"],
|
|
notes: "保留口语。",
|
|
}),
|
|
).resolves.toMatchObject({
|
|
customer_name: "客户A",
|
|
project_tags: ["朋友圈"],
|
|
});
|
|
|
|
await repository.archiveOptimizationCase(created.id);
|
|
await expect(
|
|
repository.listOptimizationCases({ include_archived: false }),
|
|
).resolves.toHaveLength(0);
|
|
|
|
await repository.restoreOptimizationCase(created.id);
|
|
await expect(repository.getOptimizationCaseDetail(created.id)).resolves.toMatchObject({
|
|
case: { id: created.id, status: "running" },
|
|
input: expect.objectContaining({ case_id: created.id }),
|
|
});
|
|
});
|
|
|
|
test("creates multiple result versions and binds publication to a version", async () => {
|
|
const repository = createSqliteRepository(dbPath);
|
|
const optimizationCase = await repository.createOptimizationCase({
|
|
case_type: "human_copy",
|
|
title: "人味文案优化:私域",
|
|
summary: "原文",
|
|
publish_target: "私域",
|
|
source_excerpt: "原文",
|
|
});
|
|
|
|
const first = await repository.createOptimizationResultVersion({
|
|
case_id: optimizationCase.id,
|
|
case_type: "human_copy",
|
|
status: "optimized",
|
|
article_job_id: null,
|
|
article_revision: null,
|
|
result_summary: "第一版",
|
|
payload: {
|
|
optimized_text: "第一版文案",
|
|
change_notes: [],
|
|
ai_taste_checks: [],
|
|
warnings: [],
|
|
},
|
|
process_summary: [],
|
|
llm_audit_summary: [],
|
|
error_stage: null,
|
|
error_summary: null,
|
|
});
|
|
const second = await repository.createOptimizationResultVersion({
|
|
case_id: optimizationCase.id,
|
|
case_type: "human_copy",
|
|
status: "optimized",
|
|
article_job_id: null,
|
|
article_revision: null,
|
|
result_summary: "第二版",
|
|
payload: {
|
|
optimized_text: "第二版文案",
|
|
change_notes: [],
|
|
ai_taste_checks: [],
|
|
warnings: [],
|
|
},
|
|
process_summary: [],
|
|
llm_audit_summary: [],
|
|
error_stage: null,
|
|
error_summary: null,
|
|
});
|
|
|
|
expect(first.version).toBe(1);
|
|
expect(second.version).toBe(2);
|
|
|
|
const publication = await repository.createPublicationRecord({
|
|
result_version_id: second.id,
|
|
job_id: null,
|
|
revision: null,
|
|
publish_target: "私域",
|
|
url: "https://example.com/private",
|
|
published_at: "2026-07-08T12:00:00.000Z",
|
|
status: "published",
|
|
notes: "客户私域发布",
|
|
});
|
|
|
|
await expect(
|
|
repository.listPublicationRecordsForResultVersion(second.id),
|
|
).resolves.toEqual([expect.objectContaining({ id: publication.id })]);
|
|
});
|
|
});
|