import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import test from "node:test"; const cronsPath = join(process.cwd(), "convex", "crons.ts"); const jobsPath = join(process.cwd(), "convex", "scheduledJobs.ts"); const auditsPath = join(process.cwd(), "convex", "audits.ts"); const schemaPath = join(process.cwd(), "convex", "schema.ts"); const cronsSource = existsSync(cronsPath) ? readFileSync(cronsPath, "utf8") : ""; const jobsSource = existsSync(jobsPath) ? readFileSync(jobsPath, "utf8") : ""; const auditsSource = readFileSync(auditsPath, "utf8"); const schemaSource = readFileSync(schemaPath, "utf8"); test("Convex crons register campaign cadence and audit lifecycle jobs", () => { assert.equal(existsSync(cronsPath), true, "convex/crons.ts should exist."); assert.match(cronsSource, /cronJobs\(\)/); assert.match(cronsSource, /internal\.scheduledJobs\.runDueCampaigns/); assert.match(cronsSource, /internal\.scheduledJobs\.runAuditLifecycle/); }); test("scheduled jobs guard single active agent run and log skipped campaign cadence", () => { assert.equal(existsSync(jobsPath), true, "convex/scheduledJobs.ts should exist."); assert.match(jobsSource, /export const runDueCampaigns = internalMutation/); assert.match(jobsSource, /canStartAgentRun/); assert.match(jobsSource, /status:\s*"canceled"[\s\S]*currentStep:\s*"campaign_cron_skipped"/); assert.match(jobsSource, /Es läuft bereits ein Agentenlauf/); assert.match(jobsSource, /ctx\.scheduler\.runAfter\(0,\s*internal\.leadDiscovery\.processCampaignRun/); }); test("audit lifecycle stores notifications and deactivates old published audits", () => { assert.match(schemaSource, /dashboardNotifications:\s*defineTable/); assert.match(schemaSource, /lifecycleNotificationAt:\s*v\.optional\(v\.number\(\)\)/); assert.match(schemaSource, /lifecycleExtendedUntil:\s*v\.optional\(v\.number\(\)\)/); assert.match(jobsSource, /export const runAuditLifecycle = internalMutation/); assert.match(jobsSource, /AUDIT_REVIEW_NOTICE_AFTER_MS/); assert.match(jobsSource, /AUDIT_AUTO_DEACTIVATE_AFTER_MS/); assert.match(jobsSource, /status:\s*"deactivated"/); assert.match(jobsSource, /Soll dieses Audit aktiv bleiben/); }); test("publishing and lifecycle controls set review due dates and allow manual extension", () => { assert.match(auditsSource, /reviewDueAt:\s*now\s*\+\s*AUDIT_REVIEW_NOTICE_AFTER_MS/); assert.match(auditsSource, /export const extendPublicAuditLifecycle = mutation/); assert.match(auditsSource, /lifecycleExtendedUntil:\s*now\s*\+\s*args\.days\s*\*\s*24\s*\*\s*60\s*\*\s*60\s*\*\s*1000/); assert.match(auditsSource, /status:\s*"published"[\s\S]*deactivatedAt:\s*undefined/); });