Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { Notification } from "../types/notification.types.js";
import type { InAppNotification, InAppStore } from "./in-app-channel.js";
import { InAppChannel } from "./in-app-channel.js";
function makeNotification(overrides: Partial<Notification> = {}): Notification {
return {
id: "notif_1",
channel: "in_app",
recipient: "user123",
subject: "Test",
body: "In-app message",
priority: "normal",
status: "sending",
createdAt: new Date().toISOString(),
...overrides,
};
}
describe("InAppChannel", () => {
it('has name "in_app"', { timeout: 5000 }, () => {
const channel = new InAppChannel();
expect(channel.name).toBe("in_app");
});
it("canSend returns true for in_app channel", { timeout: 5000 }, () => {
const channel = new InAppChannel();
expect(channel.canSend(makeNotification())).toBe(true);
});
it("canSend returns false for non in_app channel", {
timeout: 5000,
}, () => {
const channel = new InAppChannel();
expect(channel.canSend(makeNotification({ channel: "email" }))).toBe(
false
);
});
it("sends and stores notification in memory", {
timeout: 30000,
}, async () => {
const channel = new InAppChannel();
const result = await channel.send(makeNotification());
expect(result.success).toBe(true);
expect(result.messageId).toBe("notif_1");
const stored = await channel.getByRecipient("user123");
expect(stored).toHaveLength(1);
const first = stored[0];
expect(first?.body).toBe("In-app message");
});
it("stores multiple notifications per recipient", {
timeout: 30000,
}, async () => {
const channel = new InAppChannel();
await channel.send(makeNotification({ id: "n1" }));
await channel.send(makeNotification({ id: "n2", body: "Second" }));
const stored = await channel.getByRecipient("user123");
expect(stored).toHaveLength(2);
});
it("returns empty array for unknown recipient", {
timeout: 30000,
}, async () => {
const channel = new InAppChannel();
const stored = await channel.getByRecipient("unknown");
expect(stored).toHaveLength(0);
});
it("marks notification as read", { timeout: 30000 }, async () => {
const channel = new InAppChannel();
await channel.send(makeNotification());
await channel.markRead("notif_1");
const stored = await channel.getByRecipient("user123");
expect(stored[0]?.read).toBe(true);
});
it("markRead on unknown id is a no-op", { timeout: 5000 }, async () => {
const channel = new InAppChannel();
await channel.send(makeNotification());
await channel.markRead("does-not-exist");
const stored = await channel.getByRecipient("user123");
expect(stored[0]?.read).toBe(false);
});
it("uses custom store when provided", { timeout: 30000 }, async () => {
const records: InAppNotification[] = [];
const customStore: InAppStore = {
async save(n) {
records.push(n);
},
async getByRecipient() {
return records;
},
async markRead() {
/* noop */
},
};
const channel = new InAppChannel(customStore);
await channel.send(makeNotification());
expect(records).toHaveLength(1);
});
it("handles store save failure gracefully", {
timeout: 30000,
}, async () => {
const failingStore: InAppStore = {
async save() {
throw new Error("DB write failed");
},
async getByRecipient() {
return [];
},
async markRead() {
/* noop */
},
};
const channel = new InAppChannel(failingStore);
const result = await channel.send(makeNotification());
expect(result.success).toBe(false);
expect(result.error).toBe("DB write failed");
});
it("returns string body when store throws non-Error", {
timeout: 5000,
}, async () => {
const failingStore: InAppStore = {
async save() {
const literal: unknown = "boom";
throw literal;
},
async getByRecipient() {
return [];
},
async markRead() {
/* noop */
},
};
const channel = new InAppChannel(failingStore);
const result = await channel.send(makeNotification());
expect(result.success).toBe(false);
expect(result.error).toBe("boom");
});
it("notification stored as unread by default", {
timeout: 30000,
}, async () => {
const channel = new InAppChannel();
await channel.send(makeNotification());
const stored = await channel.getByRecipient("user123");
expect(stored[0]?.read).toBe(false);
});
it("preserves metadata when notification carries it", {
timeout: 5000,
}, async () => {
const channel = new InAppChannel();
await channel.send(makeNotification({ metadata: { trace: "x-1" } }));
const stored = await channel.getByRecipient("user123");
expect(stored[0]?.metadata).toEqual({ trace: "x-1" });
});
it("supports notifications without metadata and without subject", {
timeout: 5000,
}, async () => {
const channel = new InAppChannel();
const { subject: _drop, ...rest } = makeNotification();
await channel.send(rest as Notification);
const stored = await channel.getByRecipient("user123");
expect(stored[0]?.subject).toBeUndefined();
});
});