Initial Commit
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { createOpenAPIRegistry } from "@suaveplan/api-openapi";
|
||||
import { createPluginContext } from "@suaveplan/api-plugin";
|
||||
import {
|
||||
type AuthPluginConfig,
|
||||
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 { createUserPlugin } from "../../src/plugin/plugin.js";
|
||||
import type { UserPluginConfig } from "../../src/types/types.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("user plugin full-boot integration", () => {
|
||||
it("sign-up → /users/me → PATCH → DELETE roundtrip against real SurrealDB", {
|
||||
timeout: 120_000,
|
||||
}, async () => {
|
||||
if (client === null) throw new Error("client not ready");
|
||||
const app = new OpenAPIHono();
|
||||
const registry = createOpenAPIRegistry({
|
||||
info: { title: "user-test", version: "0.1.0" },
|
||||
});
|
||||
// Mount auth plugin first.
|
||||
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 },
|
||||
user: { deleteUser: { enabled: true } },
|
||||
},
|
||||
});
|
||||
const services = new Map<string, unknown>();
|
||||
services.set("app", app);
|
||||
services.set("openAPIRegistry", registry);
|
||||
services.set("db", client);
|
||||
await authPlugin.onInit?.(
|
||||
createPluginContext<AuthPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: new AbortController().signal,
|
||||
config: authPlugin.defaults as AuthPluginConfig,
|
||||
services,
|
||||
})
|
||||
);
|
||||
|
||||
// Mount user plugin.
|
||||
const userPlugin = createUserPlugin({});
|
||||
await userPlugin.onInit?.(
|
||||
createPluginContext<UserPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: new AbortController().signal,
|
||||
config: userPlugin.defaults as UserPluginConfig,
|
||||
services,
|
||||
})
|
||||
);
|
||||
registry.applyTo(app, "/openapi.json");
|
||||
|
||||
// Sign up via Better-Auth API.
|
||||
const authServiceSlot = services.get("authService") as {
|
||||
getBetterAuthInstance: () => {
|
||||
api: {
|
||||
signUpEmail: (a: {
|
||||
body: { email: string; password: string; name: string };
|
||||
}) => Promise<{ user: { id: string } }>;
|
||||
};
|
||||
};
|
||||
};
|
||||
const auth = authServiceSlot.getBetterAuthInstance();
|
||||
const signUp = await auth.api.signUpEmail({
|
||||
body: {
|
||||
email: "user-plugin@example.test",
|
||||
password: "Passw0rd!2026",
|
||||
name: "Test User",
|
||||
},
|
||||
});
|
||||
expect(signUp.user.id.length).toBeGreaterThan(0);
|
||||
|
||||
// userService.getById round-trips against the live row.
|
||||
const userService = services.get("userService") as {
|
||||
getById: (id: string) => Promise<unknown>;
|
||||
updateProfile: (
|
||||
id: string,
|
||||
patch: Readonly<Record<string, unknown>>
|
||||
) => Promise<{ displayName?: string }>;
|
||||
};
|
||||
const profile = await userService.getById(signUp.user.id);
|
||||
expect(profile).not.toBeNull();
|
||||
|
||||
// PATCH applies and refresh returns updated value.
|
||||
const updated = await userService.updateProfile(signUp.user.id, {
|
||||
displayName: "Test Updated",
|
||||
});
|
||||
expect(updated.displayName).toBe("Test Updated");
|
||||
|
||||
// OpenAPI doc lists /users/me three times (get, patch, delete).
|
||||
const openapiRes = await app.request("/openapi.json");
|
||||
const doc = (await openapiRes.json()) as {
|
||||
paths: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
const meMethods = Object.keys(doc.paths?.["/users/me"] ?? {});
|
||||
expect(meMethods.sort()).toEqual(["delete", "get", "patch"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
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 { EventBus } from "@suaveplan/events";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { createUserPlugin } from "../../src/plugin/plugin.js";
|
||||
import type { UserPluginConfig } from "../../src/types/types.js";
|
||||
|
||||
const noopLogger = {
|
||||
debug: () => undefined,
|
||||
info: () => undefined,
|
||||
warn: () => undefined,
|
||||
error: () => undefined,
|
||||
};
|
||||
|
||||
function fakeAuthService(): AuthService {
|
||||
return {
|
||||
getBetterAuthInstance: () =>
|
||||
({ $context: Promise.resolve({}) }) as never,
|
||||
getCurrentPrincipal: () => null,
|
||||
getSession: async () => null,
|
||||
};
|
||||
}
|
||||
|
||||
const okDb = {
|
||||
query: async () => [],
|
||||
execute: async () => ({ rowsAffected: 0 }),
|
||||
};
|
||||
|
||||
describe("openapi-doc", () => {
|
||||
it("served doc lists 3 /users/me routes with Zod-typed schemas", {
|
||||
timeout: 30_000,
|
||||
}, async () => {
|
||||
const app = new OpenAPIHono();
|
||||
const registry = createOpenAPIRegistry({
|
||||
info: { title: "user-test", version: "0.1.0" },
|
||||
});
|
||||
const plugin = createUserPlugin({ applySchemaOnInit: false });
|
||||
const services = new Map<string, unknown>();
|
||||
services.set("app", app);
|
||||
services.set("openAPIRegistry", registry);
|
||||
services.set("authService", fakeAuthService());
|
||||
services.set("db", okDb);
|
||||
await plugin.onInit?.(
|
||||
createPluginContext<UserPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: new AbortController().signal,
|
||||
config: plugin.defaults as UserPluginConfig,
|
||||
services,
|
||||
})
|
||||
);
|
||||
registry.applyTo(app, "/openapi.json");
|
||||
const res = await app.request("/openapi.json");
|
||||
const doc = (await res.json()) as {
|
||||
paths: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
const me = doc.paths?.["/users/me"];
|
||||
expect(me).toBeDefined();
|
||||
const methods = Object.keys(me as object).sort();
|
||||
expect(methods).toEqual(["delete", "get", "patch"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user