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,140 @@
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 { createEventsPlugin } from "../../src/plugin/plugin.js";
import type { EventsPluginConfig } 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 the migration manually — the plugin itself does not run
// migrations in this proposal (the database plugin owns that
// responsibility), so the test sets up the schema first.
const surql = await readFile(
join(__dirname, "..", "..", "surql", "tables", "event.surql"),
"utf8"
);
await adapter.query(surql);
}, 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-events integration", () => {
it("boots, creates event, records rsvp, increments count, cleans up", {
timeout: 180_000,
}, async () => {
if (adapter === null) throw new Error("fixture not ready");
// Seed creator + invitee in the user table required by the
// foreign-key constraint on event.creator / event_rsvp.user.
// The repositories only use `CREATE event SET creator = user:1`;
// SurrealDB v3 allows the record link to be soft until
// referenced, so we don't need to materialise the row, but
// creating them gives a more lifelike round-trip.
await adapter.query(
"CREATE user:creator SET name = 'C'; CREATE user:invitee SET name = 'I';"
);
// Boot the plugin.
const app = new OpenAPIHono();
const services = new Map<string, unknown>([
["db", adapter],
["app", app],
]);
const plugin = createEventsPlugin({ basePath: "/events" });
const ctx = createPluginContext<EventsPluginConfig>({
logger: noopLogger,
eventBus: new EventBus(),
signal: new AbortController().signal,
config: plugin.defaults as EventsPluginConfig,
services,
});
await plugin.onInit?.(ctx);
await plugin.onReady?.(ctx);
const eventRepo = ctx.getService("eventRepository");
const rsvpRepo = ctx.getService("rsvpRepository");
expect(eventRepo).toBeDefined();
expect(rsvpRepo).toBeDefined();
if (eventRepo === undefined || rsvpRepo === undefined) return;
const event = await eventRepo.create("user:creator", {
title: "Integration Test",
start_time: "2026-06-01T10:00:00Z",
end_time: "2026-06-01T12:00:00Z",
timezone: "UTC",
status: 1,
});
expect(event.title).toBe("Integration Test");
const found = await eventRepo.findById(String(event.id));
expect(found).not.toBeNull();
const rsvp = await rsvpRepo.upsert(String(event.id), "user:invitee", {
status: "going",
});
expect(rsvp.status).toBe("going");
await eventRepo.updateAttendeeCount(String(event.id), "going", 1);
const after = await eventRepo.findById(String(event.id));
expect(after?.attendee_count.going).toBeGreaterThanOrEqual(1);
const oldStatus = await rsvpRepo.delete(
String(event.id),
"user:invitee"
);
expect(oldStatus).toBe("going");
await eventRepo.delete(String(event.id));
const gone = await eventRepo.findById(String(event.id));
expect(gone).toBeNull();
await plugin.onClose?.(ctx);
});
});

View File

@@ -0,0 +1,34 @@
/**
* Test setup for @suaveplan/api-plugin-events.
*
* Installs a real in-memory OpenTelemetry MeterProvider BEFORE any
* source module imports so the meter resolved on first call binds to
* it. Tests that need fresh metric state per case call
* `resetEventsInstruments()` and `flushTestMetrics()` themselves.
*/
import { metrics } from "@opentelemetry/api";
import {
AggregationTemporality,
InMemoryMetricExporter,
MeterProvider,
PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
export const testInMemoryMetricExporter = new InMemoryMetricExporter(
AggregationTemporality.CUMULATIVE
);
const reader = new PeriodicExportingMetricReader({
exporter: testInMemoryMetricExporter,
exportIntervalMillis: 60_000,
});
export const testMeterProvider = new MeterProvider({ readers: [reader] });
metrics.setGlobalMeterProvider(testMeterProvider);
export async function flushTestMetrics() {
await testMeterProvider.forceFlush();
const collected = testInMemoryMetricExporter.getMetrics();
testInMemoryMetricExporter.reset();
return collected;
}