Initial Commit
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { createPluginContext } from "@suaveplan/api-plugin";
|
||||
import { EventBus } from "@suaveplan/events";
|
||||
import { SurrealAdapter } from "@suaveplan/surrealdb";
|
||||
import {
|
||||
afterAll,
|
||||
beforeAll,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
} from "@suaveplan/testing/runner";
|
||||
import { SurrealDBFixture } from "@suaveplan/testing-integration-surrealdb";
|
||||
import { createNotificationsPlugin } from "../../src/plugin/plugin.js";
|
||||
import type { NotificationsPluginConfig } from "../../src/schemas/options.js";
|
||||
|
||||
let fixture: SurrealDBFixture | null = null;
|
||||
let cfg: {
|
||||
url: string;
|
||||
namespace: string;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
} | null = null;
|
||||
let adapter: SurrealAdapter | null = null;
|
||||
|
||||
const noopLogger = {
|
||||
debug: () => undefined,
|
||||
info: () => undefined,
|
||||
warn: () => undefined,
|
||||
error: () => undefined,
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
fixture = new SurrealDBFixture();
|
||||
cfg = await fixture.start();
|
||||
adapter = new SurrealAdapter({
|
||||
url: cfg.url,
|
||||
namespace: cfg.namespace,
|
||||
database: cfg.database,
|
||||
username: cfg.username,
|
||||
password: cfg.password,
|
||||
});
|
||||
await adapter.connect();
|
||||
// Apply both migrations manually — the plugin does not own
|
||||
// migration execution.
|
||||
const notif = await readFile(
|
||||
join(__dirname, "..", "..", "surql", "tables", "notification.surql"),
|
||||
"utf8"
|
||||
);
|
||||
const device = await readFile(
|
||||
join(__dirname, "..", "..", "surql", "tables", "device_token.surql"),
|
||||
"utf8"
|
||||
);
|
||||
await adapter.query(notif);
|
||||
await adapter.query(device);
|
||||
}, 180_000);
|
||||
|
||||
afterAll(async () => {
|
||||
if (adapter !== null) {
|
||||
await adapter.disconnect();
|
||||
adapter = null;
|
||||
}
|
||||
if (fixture !== null) {
|
||||
await fixture.stop();
|
||||
fixture = null;
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
describe("@suaveplan/api-plugin-notifications integration", () => {
|
||||
it("boots, registers device, creates + reads + dismisses + deletes notification", {
|
||||
timeout: 180_000,
|
||||
}, async () => {
|
||||
if (adapter === null) throw new Error("fixture not ready");
|
||||
|
||||
// Seed recipient + site so the record-link constraints pass.
|
||||
await adapter.query(
|
||||
"CREATE user:recipient SET name = 'R'; CREATE site:main SET name = 'S';"
|
||||
);
|
||||
|
||||
const app = new OpenAPIHono();
|
||||
const services = new Map<string, unknown>([
|
||||
["db", adapter],
|
||||
["app", app],
|
||||
]);
|
||||
const plugin = createNotificationsPlugin();
|
||||
const ctx = createPluginContext<NotificationsPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: new AbortController().signal,
|
||||
config: plugin.defaults as NotificationsPluginConfig,
|
||||
services,
|
||||
});
|
||||
await plugin.onInit?.(ctx);
|
||||
await plugin.onReady?.(ctx);
|
||||
|
||||
const repo = ctx.getService("notificationRepository");
|
||||
const tokens = ctx.getService("deviceTokenRepository");
|
||||
expect(repo).toBeDefined();
|
||||
expect(tokens).toBeDefined();
|
||||
if (repo === undefined || tokens === undefined) return;
|
||||
|
||||
const created = await repo.create({
|
||||
type: "friend_request",
|
||||
recipient: "user:recipient",
|
||||
site: "site:main",
|
||||
content: "sent you a friend request",
|
||||
});
|
||||
expect(created.id).toBeDefined();
|
||||
|
||||
const fetched = await repo.findById(String(created.id));
|
||||
expect(fetched).not.toBeNull();
|
||||
|
||||
const unread = await repo.getUnreadCount("user:recipient", "site:main");
|
||||
expect(unread).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const read = await repo.markAsRead(String(created.id));
|
||||
expect(read?.read).toBe(true);
|
||||
|
||||
const dismissed = await repo.dismiss(String(created.id));
|
||||
expect(dismissed?.dismissed).toBe(true);
|
||||
|
||||
const list = await repo.findByRecipient("user:recipient", "site:main", {
|
||||
limit: 10,
|
||||
});
|
||||
expect(list.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const deleted = await repo.delete(String(created.id));
|
||||
expect(deleted).toBe(true);
|
||||
|
||||
// Device-token round-trip.
|
||||
const tok = await tokens.register("user:recipient", {
|
||||
deviceToken: `tok-${Date.now()}`,
|
||||
deviceId: "dev1",
|
||||
deviceType: "ios",
|
||||
environment: "sandbox",
|
||||
});
|
||||
expect(tok.platform).toBe("ios");
|
||||
|
||||
const active = await tokens.findActiveForUser("user:recipient");
|
||||
expect(active.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const off = await tokens.deactivate(String(tok.id));
|
||||
expect(off).toBe(true);
|
||||
|
||||
await plugin.onClose?.(ctx);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user