Initial Commit
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { createOpenAPIRegistry } from "@suaveplan/api-openapi";
|
||||
import { createPluginContext } from "@suaveplan/api-plugin";
|
||||
import type { AuthService } from "@suaveplan/api-plugin-auth";
|
||||
import { createAuthPlugin } from "@suaveplan/api-plugin-auth";
|
||||
import { betterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb";
|
||||
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 { createVaultPlugin } from "../../src/plugin/plugin.js";
|
||||
import { createMemoryDrive } from "../setup/memory-drive.js";
|
||||
|
||||
const noopLogger = {
|
||||
debug: () => undefined,
|
||||
info: () => undefined,
|
||||
warn: () => undefined,
|
||||
error: () => undefined,
|
||||
};
|
||||
|
||||
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();
|
||||
await client.execute("DEFINE TABLE OVERWRITE user SCHEMAFULL;");
|
||||
await client.execute(
|
||||
"DEFINE FIELD OVERWRITE email ON user TYPE option<string>;"
|
||||
);
|
||||
await client.execute(
|
||||
"DEFINE FIELD OVERWRITE phoneNumber ON user TYPE option<string>;"
|
||||
);
|
||||
await client.execute(
|
||||
"DEFINE FIELD OVERWRITE createdAt ON user TYPE option<datetime> DEFAULT time::now() VALUE $before OR time::now();"
|
||||
);
|
||||
await client.execute(
|
||||
"DEFINE FIELD OVERWRITE updatedAt ON user TYPE option<datetime> DEFAULT time::now() VALUE time::now();"
|
||||
);
|
||||
}, 180_000);
|
||||
|
||||
afterAll(async () => {
|
||||
if (client) {
|
||||
await client.disconnect();
|
||||
client = null;
|
||||
}
|
||||
if (fixture) {
|
||||
await fixture.stop();
|
||||
fixture = null;
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
describe("full integration boot — auth + vault against real SurrealDB", () => {
|
||||
it("sign-up → POST → GET → PATCH → DELETE /vault/items roundtrip against real SurrealDB", {
|
||||
timeout: 180_000,
|
||||
}, async () => {
|
||||
if (client === null) throw new Error("client not ready");
|
||||
const app = new OpenAPIHono();
|
||||
const registry = createOpenAPIRegistry({
|
||||
info: { title: "vault-integration", version: "0.1.0" },
|
||||
});
|
||||
const services = new Map<string, unknown>();
|
||||
services.set("app", app);
|
||||
services.set("openAPIRegistry", registry);
|
||||
services.set("db", client);
|
||||
|
||||
// 1. Auth plugin — applies schema + registers authService +
|
||||
// installs session middleware on the app.
|
||||
const authPlugin = createAuthPlugin({
|
||||
betterAuth: {
|
||||
database: betterAuthSurrealAdapter({ client }),
|
||||
secret: "test-secret-at-least-32-chars-long-for-better-auth",
|
||||
baseURL: "http://localhost:3000/auth",
|
||||
emailAndPassword: { enabled: true },
|
||||
},
|
||||
});
|
||||
await authPlugin.onInit?.(
|
||||
createPluginContext({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: new AbortController().signal,
|
||||
config: authPlugin.defaults as never,
|
||||
services,
|
||||
})
|
||||
);
|
||||
|
||||
// 2. Vault plugin — applies vault schema + registers vaultService
|
||||
// + mounts 5 routes.
|
||||
const drive = createMemoryDrive();
|
||||
const vaultPlugin = createVaultPlugin({
|
||||
storage: drive,
|
||||
applySchemaOnInit: true,
|
||||
});
|
||||
await vaultPlugin.onInit?.(
|
||||
createPluginContext({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: new AbortController().signal,
|
||||
config: vaultPlugin.defaults as never,
|
||||
services,
|
||||
})
|
||||
);
|
||||
// Error-handler so VaultPluginError → JSON envelope w/ statusCode.
|
||||
app.onError((err, c) => {
|
||||
const status =
|
||||
(err as { context?: { statusCode?: number } }).context
|
||||
?.statusCode ?? 500;
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: (err as { code?: string }).code ?? "X",
|
||||
message: (err as Error).message,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
status as 400 | 401 | 403 | 404 | 413 | 500
|
||||
);
|
||||
});
|
||||
|
||||
// 3. Sign up via Better-Auth.
|
||||
const authService = services.get("authService") as AuthService;
|
||||
expect(authService).toBeDefined();
|
||||
const auth = authService.getBetterAuthInstance();
|
||||
const signUp = (await auth.api.signUpEmail({
|
||||
body: {
|
||||
email: "vault-integration@example.test",
|
||||
password: "Passw0rd!2026",
|
||||
name: "Vault Integration",
|
||||
},
|
||||
asResponse: true,
|
||||
})) as Response;
|
||||
expect(signUp.status).toBeLessThan(400);
|
||||
const cookie = signUp.headers.get("set-cookie") ?? "";
|
||||
|
||||
// 4. POST /vault/items
|
||||
const createRes = await app.request("/vault/items", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Vault-Unlock": "MyVaultPwd-2026",
|
||||
"Content-Type": "application/json",
|
||||
Cookie: cookie.split(";")[0] ?? "",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: "API key",
|
||||
kind: "credential",
|
||||
body: "secret-body-value",
|
||||
}),
|
||||
});
|
||||
expect(createRes.status).toBe(201);
|
||||
const created = (await createRes.json()) as {
|
||||
data: { id: string; title: string; body: string };
|
||||
};
|
||||
expect(created.data.body).toBe("secret-body-value");
|
||||
const itemId = created.data.id;
|
||||
|
||||
// 5. GET /vault/items
|
||||
const listRes = await app.request("/vault/items", {
|
||||
headers: {
|
||||
"X-Vault-Unlock": "MyVaultPwd-2026",
|
||||
Cookie: cookie.split(";")[0] ?? "",
|
||||
},
|
||||
});
|
||||
expect(listRes.status).toBe(200);
|
||||
const list = (await listRes.json()) as {
|
||||
meta: { total: number };
|
||||
};
|
||||
expect(list.meta.total).toBe(1);
|
||||
|
||||
// 6. PATCH /vault/items/:id
|
||||
const patchRes = await app.request(`/vault/items/${itemId}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"X-Vault-Unlock": "MyVaultPwd-2026",
|
||||
"Content-Type": "application/json",
|
||||
Cookie: cookie.split(";")[0] ?? "",
|
||||
},
|
||||
body: JSON.stringify({ title: "Renamed" }),
|
||||
});
|
||||
expect(patchRes.status).toBe(200);
|
||||
|
||||
// 7. DELETE /vault/items/:id
|
||||
const delRes = await app.request(`/vault/items/${itemId}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"X-Vault-Unlock": "MyVaultPwd-2026",
|
||||
Cookie: cookie.split(";")[0] ?? "",
|
||||
},
|
||||
});
|
||||
expect(delRes.status).toBe(200);
|
||||
|
||||
// 8. Final list should be empty.
|
||||
const finalRes = await app.request("/vault/items", {
|
||||
headers: {
|
||||
"X-Vault-Unlock": "MyVaultPwd-2026",
|
||||
Cookie: cookie.split(";")[0] ?? "",
|
||||
},
|
||||
});
|
||||
const final = (await finalRes.json()) as { meta: { total: number } };
|
||||
expect(final.meta.total).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user