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