Files
digikedai-bot/tests/whatsapp-trial.test.ts

140 lines
5.1 KiB
TypeScript

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<typeof vi.fn>;
provisionTrial: ReturnType<typeof vi.fn>;
};
}
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");
});
});