mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 19:51:17 +00:00
187 lines
6 KiB
TypeScript
187 lines
6 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
birthdayInYear,
|
|
findDueEvents,
|
|
type ActiveTrigger,
|
|
type CustomerFacts,
|
|
} from "@/src/modules/marketing/lib/triggers";
|
|
|
|
// activatedAt als Berliner Mittag, damit der Tag eindeutig ist.
|
|
const activatedOn = (day: string) => new Date(`${day}T10:00:00Z`);
|
|
|
|
function trigger(overrides: Partial<ActiveTrigger> & Pick<ActiveTrigger, "kind">): ActiveTrigger {
|
|
return {
|
|
id: `t-${overrides.kind}`,
|
|
config: {},
|
|
activatedAt: activatedOn("2026-01-01"),
|
|
emailNodeId: "mail-1",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function customer(overrides: Partial<CustomerFacts> = {}): CustomerFacts {
|
|
return { userId: "u1", birthday: null, bookingDays: [], ...overrides };
|
|
}
|
|
|
|
describe("birthdayInYear", () => {
|
|
it("nimmt Monat und Tag aus UTC", () => {
|
|
expect(birthdayInYear(new Date("1990-05-12"), 2026)).toBe("2026-05-12");
|
|
});
|
|
|
|
it("feiert den 29.2. in anderen Jahren am 28.2.", () => {
|
|
expect(birthdayInYear(new Date("1996-02-29"), 2027)).toBe("2027-02-28");
|
|
expect(birthdayInYear(new Date("1996-02-29"), 2028)).toBe("2028-02-29");
|
|
});
|
|
});
|
|
|
|
describe("Geburtstag", () => {
|
|
const birthday = trigger({ kind: "birthday" });
|
|
|
|
it("ist am Geburtstag fällig, Zeitraum ist das Jahr", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-05-12",
|
|
triggers: [birthday],
|
|
customers: [customer({ birthday: new Date("1990-05-12") })],
|
|
});
|
|
expect(events).toEqual([
|
|
expect.objectContaining({ triggerKind: "birthday", userId: "u1", periodKey: "2026" }),
|
|
]);
|
|
});
|
|
|
|
it("holt bis zu zwei Tage nach, danach nicht mehr", () => {
|
|
const facts = [customer({ birthday: new Date("1990-05-12") })];
|
|
expect(
|
|
findDueEvents({ today: "2026-05-14", triggers: [birthday], customers: facts })
|
|
).toHaveLength(1);
|
|
expect(
|
|
findDueEvents({ today: "2026-05-15", triggers: [birthday], customers: facts })
|
|
).toHaveLength(0);
|
|
});
|
|
|
|
it("holt am 1.1. den 31.12. des Vorjahres nach", () => {
|
|
const events = findDueEvents({
|
|
today: "2027-01-01",
|
|
triggers: [birthday],
|
|
customers: [customer({ birthday: new Date("1990-12-31") })],
|
|
});
|
|
expect(events[0]?.periodKey).toBe("2026");
|
|
});
|
|
|
|
it("zählt keinen Geburtstag vor dem Einschalten", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-05-13",
|
|
triggers: [trigger({ kind: "birthday", activatedAt: activatedOn("2026-05-13") })],
|
|
customers: [customer({ birthday: new Date("1990-05-12") })],
|
|
});
|
|
expect(events).toHaveLength(0);
|
|
});
|
|
|
|
it("überspringt Kunden ohne Geburtsdatum", () => {
|
|
expect(
|
|
findDueEvents({ today: "2026-05-12", triggers: [birthday], customers: [customer()] })
|
|
).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("Meilenstein", () => {
|
|
const milestone = trigger({ kind: "milestone", config: { bookings: 3 } });
|
|
|
|
it("ist fällig, sobald der 3. Kurs vorbei ist", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-03-11",
|
|
triggers: [milestone],
|
|
customers: [customer({ bookingDays: ["2026-03-01", "2026-03-05", "2026-03-10"] })],
|
|
});
|
|
expect(events).toEqual([
|
|
expect.objectContaining({ periodKey: "3", milestoneCount: 3, triggerKind: "milestone" }),
|
|
]);
|
|
});
|
|
|
|
it("zählt kommende Kurse nicht mit", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-03-11",
|
|
triggers: [milestone],
|
|
customers: [customer({ bookingDays: ["2026-03-01", "2026-03-05", "2026-03-12"] })],
|
|
});
|
|
expect(events).toHaveLength(0);
|
|
});
|
|
|
|
it("schreibt niemanden an, der die Schwelle vor dem Einschalten erreicht hat", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-03-11",
|
|
triggers: [
|
|
trigger({
|
|
kind: "milestone",
|
|
config: { bookings: 3 },
|
|
activatedAt: activatedOn("2026-03-08"),
|
|
}),
|
|
],
|
|
customers: [
|
|
customer({ bookingDays: ["2026-03-01", "2026-03-02", "2026-03-05", "2026-03-10"] }),
|
|
],
|
|
});
|
|
expect(events).toHaveLength(0);
|
|
});
|
|
|
|
it("holt höchstens sieben Tage nach", () => {
|
|
const facts = [customer({ bookingDays: ["2026-03-01", "2026-03-02", "2026-03-03"] })];
|
|
expect(
|
|
findDueEvents({ today: "2026-03-10", triggers: [milestone], customers: facts })
|
|
).toHaveLength(1);
|
|
expect(
|
|
findDueEvents({ today: "2026-03-11", triggers: [milestone], customers: facts })
|
|
).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("Reaktivierung", () => {
|
|
const winback = trigger({ kind: "winback", config: { days: 30 } });
|
|
|
|
it("ist fällig, wenn der letzte Kurs 30 Tage her ist", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-04-30",
|
|
triggers: [winback],
|
|
customers: [customer({ bookingDays: ["2026-03-01", "2026-03-31"] })],
|
|
});
|
|
expect(events).toEqual([expect.objectContaining({ periodKey: "2026-03-31:30" })]);
|
|
});
|
|
|
|
it("schreibt niemanden an, der schon wieder gebucht hat", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-04-30",
|
|
triggers: [winback],
|
|
customers: [customer({ bookingDays: ["2026-03-31", "2026-05-02"] })],
|
|
});
|
|
expect(events).toHaveLength(0);
|
|
});
|
|
|
|
it("schreibt niemanden an, der schon vor dem Einschalten so lange weg war", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-06-01",
|
|
triggers: [
|
|
trigger({ kind: "winback", config: { days: 30 }, activatedAt: activatedOn("2026-05-15") }),
|
|
],
|
|
customers: [customer({ bookingDays: ["2026-03-31"] })],
|
|
});
|
|
expect(events).toHaveLength(0);
|
|
});
|
|
|
|
it("braucht mindestens einen besuchten Kurs", () => {
|
|
expect(
|
|
findDueEvents({ today: "2026-04-30", triggers: [winback], customers: [customer()] })
|
|
).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
it("sortiert Geburtstag vor Meilenstein vor Reaktivierung", () => {
|
|
const events = findDueEvents({
|
|
today: "2026-05-12",
|
|
triggers: [
|
|
trigger({ kind: "winback", config: { days: 30 } }),
|
|
trigger({ kind: "milestone", config: { bookings: 1 } }),
|
|
trigger({ kind: "birthday" }),
|
|
],
|
|
customers: [customer({ birthday: new Date("1990-05-12"), bookingDays: ["2026-04-11"] })],
|
|
});
|
|
expect(events.map((e) => e.triggerKind)).toEqual(["birthday", "winback"]);
|
|
});
|