198 lines
5.5 KiB
TypeScript
198 lines
5.5 KiB
TypeScript
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",
|
|
});
|
|
});
|
|
});
|