Files
neta/scripts/phase7-domain-smoke.ts
T

216 lines
7.0 KiB
TypeScript

import assert from "node:assert/strict";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import * as schema from "../server/db/schema";
import { buildChatContext, buildFinanceAnalysisContext, buildProjectRiskContext } from "../server/ai/context";
import type { DomainActor } from "../server/domain/actor";
import { DomainError } from "../server/domain/errors";
import { DomainService } from "../server/services/domain";
const databasePath = process.argv[2];
assert.ok(databasePath, "Database path is required");
const sqlite = new Database(databasePath);
sqlite.pragma("foreign_keys = ON");
const db = drizzle({ client: sqlite, schema });
let generatedId = 0;
const service = new DomainService(db, () => `phase7-generated-${++generatedId}`);
const owner: DomainActor = {
authUserId: "phase7-owner",
role: "freelancer",
clientId: null,
disabled: false,
};
const otherOwner: DomainActor = {
authUserId: "phase7-other-owner",
role: "freelancer",
clientId: null,
disabled: false,
};
const clientActor: DomainActor = {
authUserId: "phase7-client-user",
role: "client",
clientId: "phase7-client",
disabled: false,
};
try {
for (const actor of [owner, otherOwner, clientActor]) {
db.insert(schema.user).values({
id: actor.authUserId,
name: actor.authUserId,
email: `${actor.authUserId}@example.com`,
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
}).run();
}
service.createClient(owner, {
id: "phase7-client",
name: "Visible Client",
email: "visible@example.com",
});
service.createClient(otherOwner, {
id: "phase7-foreign-client",
name: "Foreign Secret Client",
});
service.createProject(owner, {
id: "phase7-project",
clientId: "phase7-client",
name: "Visible Project",
status: "active",
budgetAmountMinor: 250_000,
currency: "TRY",
});
service.createProject(otherOwner, {
id: "phase7-foreign-project",
clientId: "phase7-foreign-client",
name: "Foreign Secret Project",
status: "active",
});
service.createTask(owner, {
id: "phase7-task",
clientId: "phase7-client",
projectId: "phase7-project",
title: "Visible Task",
status: "done",
});
service.createFinanceTransaction(owner, {
id: "phase7-finance",
type: "income",
amountMinor: 12_345,
currency: "TRY",
transactionDate: "2026-07-17",
paymentStatus: "paid",
});
service.createFinanceTransaction(otherOwner, {
id: "phase7-foreign-finance",
type: "income",
amountMinor: 999_999,
currency: "TRY",
transactionDate: "2026-07-17",
paymentStatus: "paid",
description: "Foreign Secret Finance",
});
service.saveJournalEntry(owner, {
id: "phase7-journal",
entryDate: "2026-07-17",
moodScore: 4,
note: "Visible journal note",
});
const chatContext = buildChatContext(service, owner, new Date("2026-07-17T12:00:00.000Z"));
assert.match(chatContext, /Visible Project/);
assert.match(chatContext, /Visible Task/);
assert.match(chatContext, /Visible journal note/);
assert.doesNotMatch(chatContext, /Foreign Secret/);
const financeContext = buildFinanceAnalysisContext(
service,
owner,
new Date("2026-07-17T12:00:00.000Z"),
);
assert.equal(financeContext.hasData, true);
assert.match(financeContext.text, /123\.45/);
assert.doesNotMatch(financeContext.text, /9999\.99|Foreign Secret/);
assert.match(buildProjectRiskContext(service, owner, "phase7-project"), /Visible Client/);
assertDomainError(
() => buildProjectRiskContext(service, owner, "phase7-foreign-project"),
"NOT_FOUND",
);
service.createChatSession(owner, { id: "phase7-chat", title: "Owner chat" });
service.addChatMessage(owner, {
id: "phase7-message",
sessionId: "phase7-chat",
role: "user",
content: "Owner question",
});
assert.equal(service.listChatSessions(owner).length, 1);
assert.equal(service.listChatMessages(owner, "phase7-chat")[0]?.content, "Owner question");
assertDomainError(() => service.getChatSession(otherOwner, "phase7-chat"), "NOT_FOUND");
assertDomainError(() => service.listChatMessages(otherOwner, "phase7-chat"), "NOT_FOUND");
assertDomainError(() => service.deleteChatSession(otherOwner, "phase7-chat"), "NOT_FOUND");
service.deleteChatSession(owner, "phase7-chat");
const remainingMessages = sqlite
.prepare("select count(*) as value from chat_messages where session_id = ?")
.get("phase7-chat") as { value: number };
assert.equal(
remainingMessages.value,
0,
"Chat session deletion must cascade to messages",
);
service.createProposal(owner, {
id: "phase7-proposal",
clientId: "phase7-client",
projectId: "phase7-project",
title: "Proposal",
amountMinor: 100_00,
});
service.createContract(owner, {
id: "phase7-contract",
proposalId: "phase7-proposal",
clientId: "phase7-client",
title: "Contract",
});
service.createInvoice(owner, {
id: "phase7-invoice",
clientId: "phase7-client",
projectId: "phase7-project",
invoiceNumber: "P7-001",
amountMinor: 100_00,
issueDate: "2026-07-17",
});
service.createSubscription(owner, {
id: "phase7-subscription",
name: "Hosting",
amountMinor: 500_00,
});
assert.equal(service.listProposals(owner).length, 1);
assert.equal(service.updateProposal(owner, "phase7-proposal", { status: "sent" }).status, "sent");
assert.equal(service.listContracts(owner).length, 1);
assert.equal(service.updateContract(owner, "phase7-contract", { status: "active" }).status, "active");
assert.equal(service.listInvoices(owner).length, 1);
assert.equal(service.updateInvoice(owner, "phase7-invoice", { status: "paid" }).status, "paid");
assert.equal(service.listSubscriptions(owner).length, 1);
assert.equal(
service.updateSubscription(owner, "phase7-subscription", { status: "cancelled" }).status,
"cancelled",
);
for (const run of [
() => service.getProposal(otherOwner, "phase7-proposal"),
() => service.updateContract(otherOwner, "phase7-contract", { status: "active" }),
() => service.deleteInvoice(otherOwner, "phase7-invoice"),
() => service.getSubscription(otherOwner, "phase7-subscription"),
]) {
assertDomainError(run, "NOT_FOUND");
}
assertDomainError(() => service.listProposals(clientActor), "FORBIDDEN");
service.deleteContract(owner, "phase7-contract");
service.deleteProposal(owner, "phase7-proposal");
service.deleteInvoice(owner, "phase7-invoice");
service.deleteSubscription(owner, "phase7-subscription");
assert.deepEqual(
[
service.listContracts(owner).length,
service.listProposals(owner).length,
service.listInvoices(owner).length,
service.listSubscriptions(owner).length,
],
[0, 0, 0, 0],
);
console.log("Phase 7 domain smoke passed: owner-scoped chat, AI context and business CRUD verified.");
} finally {
sqlite.close();
}
function assertDomainError(run: () => unknown, code: DomainError["code"]) {
assert.throws(run, (error) => error instanceof DomainError && error.code === code);
}