import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import test from "node:test"; const outreachSource = readFileSync( join(process.cwd(), "convex", "outreach.ts"), "utf8", ); const schemaSource = readFileSync( join(process.cwd(), "convex", "schema.ts"), "utf8", ); test("outreach schema stores follow-up due and do-not-contact recheck dates as optional migration-safe fields", () => { assert.match(schemaSource, /followUpDueAt:\s*v\.optional\(v\.number\(\)\)/); assert.match(schemaSource, /parentOutreachId:\s*v\.optional\(v\.id\("outreachRecords"\)\)/); assert.match(schemaSource, /doNotContactUntil:\s*v\.optional\(v\.number\(\)\)/); }); test("successful initial sends create one follow-up draft for manual approval", () => { assert.match(outreachSource, /createFollowUpDraftAfterInitialSend/); assert.match(outreachSource, /followUpDueAt:\s*sentAt\s*\+\s*FOLLOW_UP_DUE_DELAY_MS/); assert.match(outreachSource, /approvalStatus:\s*"draft"[\s\S]*sendStatus:\s*"not_sent"/); assert.match(outreachSource, /parentOutreachId:\s*outreach\._id/); }); test("manual sales status mutation updates lead suppression states", () => { assert.match(outreachSource, /export const updateManualSalesStatus = mutation/); assert.match(outreachSource, /salesStatus:\s*manualSalesStatus/); assert.match(outreachSource, /reply_received[\s\S]*leadPatch\.contactStatus\s*=\s*"replied"/); assert.match(outreachSource, /not_interested[\s\S]*outreachPatch\.responseStatus\s*=\s*"no_interest"/); assert.match(outreachSource, /do_not_pursue[\s\S]*outreachPatch\.doNotContactUntil\s*=\s*now\s*\+\s*DO_NOT_CONTACT_RECHECK_MS/); }); test("lead funnel query exposes do-not-contact recheck dates", () => { const leadsSource = readFileSync( join(process.cwd(), "convex", "leads.ts"), "utf8", ); assert.match(leadsSource, /doNotContactUntil:\s*latestOutreach\.doNotContactUntil/); });