import { describe, it, expect, vi, beforeEach } from "vitest"; import { createWhatsAppTrial } from "../src/channels/whatsapp/trial.js"; import type { Db } from "../src/db/db.js"; import type { ConversationMemory } from "../src/ai/memory/memory.js"; import type { NocoProvisioner } from "../src/integrations/nocodb/provision.js"; function makeProvisioner( accounts: { id: number; username: string }[] = [], ) { const probeExisting = vi.fn(async () => accounts); const provisionTrial = vi.fn(async () => ({ ok: true, username: "abc123", password: "pw", message: "ok", })); return { probeExisting, provisionTrial, } as unknown as NocoProvisioner & { probeExisting: ReturnType; provisionTrial: ReturnType; }; } function makeTrial(provisioner?: NocoProvisioner) { const db = { getOrCreateUserId: vi.fn(async () => 1), } as unknown as Db; const memory = { recall: vi.fn(async () => undefined), remember: vi.fn(async () => {}), recallAll: vi.fn(async () => ({})), } as unknown as ConversationMemory; const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never; const trial = createWhatsAppTrial({ db, memory, provisioner, logger }); return { trial, db, memory }; } describe("createWhatsAppTrial", () => { beforeEach(() => vi.clearAllMocks()); it("starts the flow from a paid SKU with trial intent, asking for username (0 accounts)", async () => { const prov = makeProvisioner(); const { trial } = makeTrial(prov); const reply = await trial.handleMessage("I want try this CZH03", "60103181872"); expect(reply).toContain("FREECZH03"); expect(reply).toContain("username"); expect(prov.probeExisting).toHaveBeenCalledWith({ telegramUserId: "60103181872" }); }); it("starts the flow from a bare FREE-prefixed SKU", async () => { const { trial } = makeTrial(makeProvisioner()); const reply = await trial.handleMessage("FreeCZH03", "60103181872"); expect(reply).toContain("FREECZH03"); }); it("starts the flow from a product-page URL", async () => { const { trial } = makeTrial(makeProvisioner()); const reply = await trial.handleMessage( "I want try https://www.digikedai.com/products/czh03", "60103181872", ); expect(reply).toContain("FREECZH03"); }); it("asks which SKU/URL when trial intent has no SKU", async () => { const { trial } = makeTrial(makeProvisioner()); const reply = await trial.handleMessage("I want to try the baking course", "60103181872"); expect(reply).toContain("SKU"); expect(reply).toContain("digikedai.com/products"); }); it("confirms reuse for a single existing account, then provisions to it", async () => { const prov = makeProvisioner([{ id: 7, username: "lover" }]); const { trial } = makeTrial(prov); const r1 = await trial.handleMessage("I want try CZH03", "60103181872"); expect(r1).toContain("lover"); const r2 = await trial.handleMessage("ok", "60103181872"); expect(prov.provisionTrial).toHaveBeenCalledWith( expect.objectContaining({ customerId: 7, sku: "FREECZH03" }), ); expect(r2).toContain("abc123"); }); it("lists multiple accounts and defaults to the first on consent", async () => { const prov = makeProvisioner([ { id: 7, username: "lover" }, { id: 9, username: "other" }, ]); const { trial } = makeTrial(prov); const r1 = await trial.handleMessage("FreeCZH03", "60103181872"); expect(r1).toContain("1. lover"); expect(r1).toContain("2. other"); const r2 = await trial.handleMessage("ok", "60103181872"); expect(prov.provisionTrial).toHaveBeenCalledWith( expect.objectContaining({ customerId: 7 }), ); expect(r2).toContain("abc123"); }); it("picks the numbered account when given an index", async () => { const prov = makeProvisioner([ { id: 7, username: "lover" }, { id: 9, username: "other" }, ]); const { trial } = makeTrial(prov); await trial.handleMessage("FreeCZH03", "60103181872"); await trial.handleMessage("2", "60103181872"); expect(prov.provisionTrial).toHaveBeenCalledWith( expect.objectContaining({ customerId: 9 }), ); }); it("provisions a new account with the supplied username", async () => { const prov = makeProvisioner(); const { trial } = makeTrial(prov); await trial.handleMessage("I want try CZH03", "60103181872"); const r2 = await trial.handleMessage("abc123", "60103181872"); expect(prov.provisionTrial).toHaveBeenCalledWith( expect.objectContaining({ username: "abc123", sku: "FREECZH03" }), ); expect(r2).toContain("abc123"); }); it("lets a non-trial SKU question fall through to the LLM", async () => { const { trial } = makeTrial(makeProvisioner()); const reply = await trial.handleMessage("CZH03 多少钱", "60103181872"); expect(reply).toBeUndefined(); }); it("degrades to the username form when no provisioner is configured", async () => { const { trial } = makeTrial(undefined); const reply = await trial.handleMessage("I want try CZH03", "60103181872"); expect(reply).toContain("username"); }); });