mirror of
https://github.com/devkitshq/notifkit.git
synced 2026-10-19 18:24:52 +02:00
154 lines
5.4 KiB
TypeScript
154 lines
5.4 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
resolveStepTarget,
|
|
buildStepNotifyPayload,
|
|
SuspendExecutionError,
|
|
} from "@/workflows/sdk.js";
|
|
import type { WorkflowNotifyInput } from "@/contracts/sdk.js";
|
|
|
|
describe("Workflow SDK Target & Payload Resolution (src/workflows/sdk.ts)", () => {
|
|
describe("resolveStepTarget", () => {
|
|
it("returns segment target when segment is provided", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "promo",
|
|
segment: "power-users",
|
|
};
|
|
const target = resolveStepTarget(stepArgs, null);
|
|
expect(target).toEqual({ type: "segment", segment: "power-users" });
|
|
});
|
|
|
|
it("returns topic target when topic is provided", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "security-alert",
|
|
topic: "security",
|
|
};
|
|
const target = resolveStepTarget(stepArgs, null);
|
|
expect(target).toEqual({ type: "topic", topic: "security" });
|
|
});
|
|
|
|
it("returns user target when user is provided as a string", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "welcome",
|
|
user: "user-123",
|
|
};
|
|
const target = resolveStepTarget(stepArgs, null);
|
|
expect(target).toEqual({ type: "user", userId: "user-123" });
|
|
});
|
|
|
|
it("returns user target when user is provided as an object with id", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "welcome",
|
|
user: { id: "user-456" } as any,
|
|
};
|
|
const target = resolveStepTarget(stepArgs, null);
|
|
expect(target).toEqual({ type: "user", userId: "user-456" });
|
|
});
|
|
|
|
it("throws an error when user is provided as an array", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "broadcast",
|
|
user: ["user-1", "user-2"] as any,
|
|
};
|
|
expect(() => resolveStepTarget(stepArgs, null)).toThrowError(
|
|
/step\.notify\(\) takes a single `user`/,
|
|
);
|
|
});
|
|
|
|
it("falls back to instanceInput user id when step target is omitted", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "daily-digest",
|
|
};
|
|
const instanceInput = { user: { id: "user-inherited-789" } };
|
|
const target = resolveStepTarget(stepArgs, instanceInput);
|
|
expect(target).toEqual({ type: "user", userId: "user-inherited-789" });
|
|
});
|
|
|
|
it("throws an error when step names no target and instanceInput has no user id", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "daily-digest",
|
|
};
|
|
expect(() => resolveStepTarget(stepArgs, {})).toThrowError(/step\.notify\(\) has no target/);
|
|
expect(() => resolveStepTarget(stepArgs, null)).toThrowError(
|
|
/step\.notify\(\) has no target/,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("buildStepNotifyPayload", () => {
|
|
it("maps all fields correctly with default values", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "welcome-template",
|
|
user: "user-001",
|
|
};
|
|
|
|
const payload = buildStepNotifyPayload(stepArgs, null, "proj-1", "idem-key-1");
|
|
|
|
expect(payload).toEqual({
|
|
projectId: "proj-1",
|
|
target: { type: "user", userId: "user-001" },
|
|
templateId: "welcome-template",
|
|
priority: "normal",
|
|
channels: undefined,
|
|
data: {},
|
|
aiPrompts: undefined,
|
|
fallback: false,
|
|
scheduledAt: undefined,
|
|
idempotencyKey: "idem-key-1",
|
|
});
|
|
});
|
|
|
|
it("maps custom options (sendAt -> scheduledAt, priority, channels, fallback, aiPrompts)", () => {
|
|
const stepArgs: WorkflowNotifyInput = {
|
|
template: "alert-template",
|
|
user: "user-002",
|
|
priority: "critical",
|
|
channels: ["sms", "email"],
|
|
data: { balance: 100 },
|
|
aiPrompts: { summary: "Summarize this account balance" },
|
|
fallback: true,
|
|
sendAt: "2026-08-20T10:00:00Z",
|
|
};
|
|
|
|
const payload = buildStepNotifyPayload(stepArgs, null, "proj-2");
|
|
|
|
expect(payload).toEqual({
|
|
projectId: "proj-2",
|
|
target: { type: "user", userId: "user-002" },
|
|
templateId: "alert-template",
|
|
priority: "critical",
|
|
channels: ["sms", "email"],
|
|
data: { balance: 100 },
|
|
aiPrompts: { summary: "Summarize this account balance" },
|
|
fallback: true,
|
|
scheduledAt: "2026-08-20T10:00:00Z",
|
|
idempotencyKey: undefined,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("SuspendExecutionError", () => {
|
|
it("constructs error with reason and payload correctly", () => {
|
|
const err = new SuspendExecutionError("wait", { durationSeconds: 60 });
|
|
expect(err.name).toBe("SuspendExecutionError");
|
|
expect(err.message).toBe("Execution suspended for wait");
|
|
expect(err.reason).toBe("wait");
|
|
expect(err.payload).toEqual({ durationSeconds: 60 });
|
|
});
|
|
});
|
|
|
|
describe("WorkflowRegistry & workflow helper", () => {
|
|
it("registers and retrieves workflow handlers", async () => {
|
|
const { workflowRegistry, workflow } = await import("@/workflows/index.js");
|
|
const dummyHandler = async () => {};
|
|
|
|
workflowRegistry.register("test-registry-direct", dummyHandler);
|
|
expect(workflowRegistry.get("test-registry-direct")).toBe(dummyHandler);
|
|
|
|
workflow("test-registry-helper", dummyHandler);
|
|
expect(workflowRegistry.get("test-registry-helper")).toBe(dummyHandler);
|
|
|
|
expect(workflowRegistry.get("non-existent-workflow")).toBeUndefined();
|
|
});
|
|
});
|
|
});
|