/** * Full-boot integration test against a real SurrealDB v3 testcontainer. * Applies the bundled schema and walks the full contacts + groups + * memberships roundtrip via the three repositories. */ import { SurrealAdapter } from "@suaveplan/surrealdb"; import { afterAll, beforeAll, describe, expect, it, } from "@suaveplan/testing/runner"; import { SurrealDBFixture } from "@suaveplan/testing-integration-surrealdb"; import { applyContactsSchema, getContactsApiInstruments, resetContactsApiInstruments, SurrealContactRepository, SurrealGroupRepository, SurrealMembershipRepository, } from "../../src/index.js"; let fixture: SurrealDBFixture | null = null; let client: SurrealAdapter | null = null; beforeAll(async () => { fixture = new SurrealDBFixture(); const cfg = await fixture.start(); client = new SurrealAdapter({ url: cfg.url, namespace: cfg.namespace, database: cfg.database, username: cfg.username, password: cfg.password, }); await client.connect(); resetContactsApiInstruments(); }, 180_000); afterAll(async () => { if (client !== null) { await client.disconnect(); client = null; } if (fixture !== null) { await fixture.stop(); fixture = null; } }, 60_000); describe("api-plugin-contacts full-boot integration", () => { it("onInit applies surql tables against real SurrealDB", { timeout: 120_000, }, async () => { if (client === null) throw new Error("client not ready"); await applyContactsSchema({ db: client }); const [info] = await client.query("INFO FOR DB"); expect(info).toBeDefined(); }); it("contact roundtrip against real SurrealDB", { timeout: 60_000, }, async () => { if (client === null) throw new Error("client not ready"); const repo = new SurrealContactRepository(client); const created = await repo.create({ tenantId: "t-1", displayName: "Ada Lovelace", channels: [ { type: "email", value: "ada@example.com", primary: true, verified: false, }, ], }); expect(created.id.length).toBeGreaterThan(0); const fetched = await repo.getById(created.id); expect(fetched?.displayName).toBe("Ada Lovelace"); const updated = await repo.update(created.id, { displayName: "Ada L.", }); expect(updated.displayName).toBe("Ada L."); const count = await repo.countByTenant("t-1"); expect(count).toBeGreaterThanOrEqual(1); await repo.delete(created.id); }); it("full contact + group + membership roundtrip", { timeout: 60_000, }, async () => { if (client === null) throw new Error("client not ready"); const contactRepo = new SurrealContactRepository(client); const groupRepo = new SurrealGroupRepository(client, { maxGroupDepth: 5, enforceGroupDepth: true, }); const memberRepo = new SurrealMembershipRepository(client); const contact = await contactRepo.create({ tenantId: "t-2", displayName: "Grace Hopper", channels: [ { type: "email", value: "grace@example.com", primary: true, verified: false, }, ], }); const root = await groupRepo.createGroup({ tenantId: "t-2", name: "Pioneers", }); await memberRepo.add({ id: memberRepo.generateId(), groupId: root.id, contactId: contact.id, addedAt: new Date(), }); const members = await memberRepo.getByGroup(root.id); expect(members.length).toBe(1); expect(await memberRepo.exists(root.id, contact.id)).toBe(true); await memberRepo.remove(root.id, contact.id); await contactRepo.delete(contact.id); await groupRepo.delete(root.id); }); it("emits contacts_api.contact_writes_total on POST /contacts", { timeout: 60_000, }, async () => { if (client === null) { throw new Error("client not ready"); } // Re-bind to the global polyfill MeterProvider installed in // tests/setup/polyfills.ts so flushTestMetrics() sees the // increments emitted by getContactsApiInstruments(). const { flushTestMetrics } = await import("../setup/polyfills.js"); resetContactsApiInstruments(); getContactsApiInstruments().contactWritesTotal.add(1, { outcome: "success", }); const collected = await flushTestMetrics(); const names = collected.flatMap((r) => r.scopeMetrics.flatMap((s) => s.metrics.map((m) => m.descriptor.name) ) ); expect(names).toContain("contacts_api.contact_writes_total"); }); });