feat: add sqlite persistence
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { createDatabase } from "../connection";
|
||||
import { initializeSchema } from "../schema";
|
||||
import {
|
||||
createArticleJob,
|
||||
createBrandTemplate,
|
||||
getArticleJob,
|
||||
getBrandTemplate,
|
||||
getFactCard,
|
||||
getLatestOptimizedArticle,
|
||||
getLatestQaReport,
|
||||
listBrandTemplates,
|
||||
saveFactCard,
|
||||
saveOptimizedArticle,
|
||||
saveQaReport,
|
||||
} from "../repositories";
|
||||
|
||||
describe("sqlite repositories", () => {
|
||||
let tempDir: string;
|
||||
let dbPath: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = mkdtempSync(join(tmpdir(), "geo-agent-db-"));
|
||||
dbPath = join(tempDir, "test.db");
|
||||
const db = createDatabase(dbPath);
|
||||
initializeSchema(db);
|
||||
db.close();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("initializes all required tables", () => {
|
||||
const db = createDatabase(dbPath);
|
||||
const tables = db
|
||||
.prepare(
|
||||
"select name from sqlite_master where type = 'table' order by name",
|
||||
)
|
||||
.all()
|
||||
.map((row) => (row as { name: string }).name);
|
||||
db.close();
|
||||
|
||||
expect(tables).toEqual([
|
||||
"article_jobs",
|
||||
"brand_templates",
|
||||
"fact_cards",
|
||||
"optimized_articles",
|
||||
"qa_reports",
|
||||
]);
|
||||
});
|
||||
|
||||
it("inserts and fetches a brand template", () => {
|
||||
const created = createBrandTemplate(dbPath, {
|
||||
brand_name: "Example",
|
||||
company_full_name: "Example Technology Co., Ltd.",
|
||||
company_short_names: ["Example Tech"],
|
||||
product_names: ["Example GEO"],
|
||||
target_industries: ["GEO optimization"],
|
||||
target_audience: ["Marketing teams"],
|
||||
verified_claims: ["Eight years of experience"],
|
||||
forbidden_claims: ["Industry first"],
|
||||
tone_rules: { official_site: "official voice" },
|
||||
});
|
||||
|
||||
expect(getBrandTemplate(dbPath, created.id)).toMatchObject({
|
||||
id: created.id,
|
||||
brand_name: "Example",
|
||||
company_short_names: ["Example Tech"],
|
||||
});
|
||||
expect(listBrandTemplates(dbPath)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("inserts and fetches an article job", () => {
|
||||
const job = createArticleJob(dbPath, {
|
||||
source_title: "Original Title",
|
||||
source_body: "Original body",
|
||||
image_inputs: [{ type: "description", content: "Dashboard" }],
|
||||
publish_platform: "official_site",
|
||||
user_instructions: "Stay factual",
|
||||
});
|
||||
|
||||
expect(getArticleJob(dbPath, job.id)).toMatchObject({
|
||||
id: job.id,
|
||||
source_title: "Original Title",
|
||||
image_inputs: [{ type: "description", content: "Dashboard" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("saves a confirmed fact card for a job", () => {
|
||||
const job = createArticleJob(dbPath, {
|
||||
source_title: "Original Title",
|
||||
source_body: "Original body",
|
||||
image_inputs: [],
|
||||
publish_platform: "media_article",
|
||||
user_instructions: "",
|
||||
});
|
||||
|
||||
saveFactCard(dbPath, job.id, {
|
||||
company_full_name: "Example Technology Co., Ltd.",
|
||||
company_short_names: ["Example"],
|
||||
brand_names: ["Example"],
|
||||
product_names: ["Example GEO"],
|
||||
target_industry: "GEO optimization",
|
||||
target_audience: "Marketing teams",
|
||||
experience_years: 8,
|
||||
core_claims: ["Eight years of experience"],
|
||||
forbidden_claims: [],
|
||||
image_topics: [],
|
||||
uncertain_items: [],
|
||||
is_ready_for_optimization: true,
|
||||
confirmed_by_user: true,
|
||||
});
|
||||
|
||||
expect(getFactCard(dbPath, job.id)).toMatchObject({
|
||||
job_id: job.id,
|
||||
company_full_name: "Example Technology Co., Ltd.",
|
||||
confirmed_by_user: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("increments optimized article revisions", () => {
|
||||
const job = createArticleJob(dbPath, {
|
||||
source_title: "Original Title",
|
||||
source_body: "Original body",
|
||||
image_inputs: [],
|
||||
publish_platform: "comparison_review",
|
||||
user_instructions: "",
|
||||
});
|
||||
|
||||
const first = saveOptimizedArticle(dbPath, job.id, {
|
||||
title: "Optimized v1",
|
||||
summary: "Summary",
|
||||
body_markdown: "Body",
|
||||
image_suggestions: [],
|
||||
changed_sections: ["title"],
|
||||
requires_user_confirmation: [],
|
||||
});
|
||||
const second = saveOptimizedArticle(dbPath, job.id, {
|
||||
title: "Optimized v2",
|
||||
summary: "Summary",
|
||||
body_markdown: "Body",
|
||||
image_suggestions: [],
|
||||
changed_sections: ["title"],
|
||||
requires_user_confirmation: [],
|
||||
});
|
||||
|
||||
expect(first.revision).toBe(1);
|
||||
expect(second.revision).toBe(2);
|
||||
expect(getLatestOptimizedArticle(dbPath, job.id)?.title).toBe(
|
||||
"Optimized v2",
|
||||
);
|
||||
});
|
||||
|
||||
it("saves and fetches the latest QA report by job and revision", () => {
|
||||
const job = createArticleJob(dbPath, {
|
||||
source_title: "Original Title",
|
||||
source_body: "Original body",
|
||||
image_inputs: [],
|
||||
publish_platform: "recommendation_list",
|
||||
user_instructions: "",
|
||||
});
|
||||
saveOptimizedArticle(dbPath, job.id, {
|
||||
title: "Optimized",
|
||||
summary: "Summary",
|
||||
body_markdown: "Body",
|
||||
image_suggestions: [],
|
||||
changed_sections: [],
|
||||
requires_user_confirmation: [],
|
||||
});
|
||||
|
||||
saveQaReport(dbPath, job.id, 1, {
|
||||
overall_status: "warn",
|
||||
checks: [
|
||||
{
|
||||
rule_id: "image_text_match",
|
||||
status: "warn",
|
||||
evidence: "Image confidence is low.",
|
||||
reason: "Image descriptions are sparse.",
|
||||
suggested_fix: "Review image placement.",
|
||||
target_agent: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(getLatestQaReport(dbPath, job.id)).toMatchObject({
|
||||
job_id: job.id,
|
||||
revision: 1,
|
||||
overall_status: "warn",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user