155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
/**
|
|
* 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<unknown[]>("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");
|
|
});
|
|
});
|