145 lines
5.3 KiB
TypeScript
145 lines
5.3 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import type { Auth } from "better-auth";
|
|
import { flushTestMetrics } from "../../tests/setup/polyfills.js";
|
|
import { resetAuthInstruments } from "../otel/instruments.js";
|
|
import { buildAuthRoutes } from "./build-routes.js";
|
|
|
|
function makeAuthStub(status = 200): Auth {
|
|
return {
|
|
handler: async () =>
|
|
new Response(JSON.stringify({ ok: true }), {
|
|
status,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
} as unknown as Auth;
|
|
}
|
|
|
|
describe("buildAuthRoutes", () => {
|
|
it("returns 9 route entries", { timeout: 5000 }, () => {
|
|
const entries = buildAuthRoutes(makeAuthStub());
|
|
expect(entries).toHaveLength(9);
|
|
});
|
|
|
|
it("every route has typed body/query/response", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const entries = buildAuthRoutes(makeAuthStub());
|
|
for (const { route } of entries) {
|
|
expect(typeof route.method).toBe("string");
|
|
expect(typeof route.path).toBe("string");
|
|
// responses[200] schema exists
|
|
const ok = (
|
|
route.responses as Record<
|
|
string | number,
|
|
{
|
|
content?: { "application/json"?: { schema: unknown } };
|
|
}
|
|
>
|
|
)["200"];
|
|
expect(ok?.content?.["application/json"]?.schema).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it("POST routes carry a body schema", { timeout: 5000 }, () => {
|
|
const entries = buildAuthRoutes(makeAuthStub());
|
|
const bodyRoutes = [
|
|
"/sign-up",
|
|
"/sign-in",
|
|
"/forgot-password",
|
|
"/reset-password",
|
|
"/verify-email",
|
|
];
|
|
for (const path of bodyRoutes) {
|
|
const entry = entries.find((e) => e.route.path === path);
|
|
expect(entry).toBeDefined();
|
|
const req = entry?.route.request as
|
|
| { body?: { content?: { "application/json"?: unknown } } }
|
|
| undefined;
|
|
expect(req?.body?.content?.["application/json"]).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it("OAuth routes carry params + query schemas", { timeout: 5000 }, () => {
|
|
const entries = buildAuthRoutes(makeAuthStub());
|
|
const callback = entries.find(
|
|
(e) => e.route.path === "/oauth/{provider}/callback"
|
|
);
|
|
const req = callback?.route.request as
|
|
| { params?: unknown; query?: unknown }
|
|
| undefined;
|
|
expect(req?.params).toBeDefined();
|
|
expect(req?.query).toBeDefined();
|
|
});
|
|
|
|
it("sign-up handler emits signups_total counter on success", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
resetAuthInstruments();
|
|
const entries = buildAuthRoutes(makeAuthStub(200));
|
|
const signUp = entries.find((e) => e.route.path === "/sign-up");
|
|
const res = await signUp!.handler({
|
|
req: { raw: new Request("http://x.test/sign-up") },
|
|
} as never);
|
|
expect(res).toBeInstanceOf(Response);
|
|
const metrics = await flushTestMetrics();
|
|
const names = metrics
|
|
.flatMap((m) => m.scopeMetrics)
|
|
.flatMap((s) => s.metrics)
|
|
.map((m) => m.descriptor.name);
|
|
expect(names).toContain("auth.signups_total");
|
|
});
|
|
|
|
it("sign-up handler skips signups counter on non-2xx", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
resetAuthInstruments();
|
|
const entries = buildAuthRoutes(makeAuthStub(400));
|
|
const signUp = entries.find((e) => e.route.path === "/sign-up");
|
|
const res = await signUp!.handler({
|
|
req: { raw: new Request("http://x.test/sign-up") },
|
|
} as never);
|
|
expect(res).toBeInstanceOf(Response);
|
|
});
|
|
|
|
it("sign-in handler emits failure outcome on non-2xx", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
resetAuthInstruments();
|
|
const entries = buildAuthRoutes(makeAuthStub(401));
|
|
const signIn = entries.find((e) => e.route.path === "/sign-in");
|
|
const res = await signIn!.handler({
|
|
req: { raw: new Request("http://x.test/sign-in") },
|
|
} as never);
|
|
expect(res.status).toBe(401);
|
|
const metrics = await flushTestMetrics();
|
|
const names = metrics
|
|
.flatMap((m) => m.scopeMetrics)
|
|
.flatMap((s) => s.metrics)
|
|
.map((m) => m.descriptor.name);
|
|
expect(names).toContain("auth.signins_total");
|
|
});
|
|
|
|
it("session handler delegates to auth.handler", {
|
|
timeout: 5000,
|
|
}, async () => {
|
|
const entries = buildAuthRoutes(makeAuthStub(200));
|
|
const session = entries.find((e) => e.route.path === "/session");
|
|
const res = await session!.handler({
|
|
req: { raw: new Request("http://x.test/session") },
|
|
} as never);
|
|
expect(res.status).toBe(200);
|
|
});
|
|
|
|
it("sign-in handler emits success outcome on 2xx", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
resetAuthInstruments();
|
|
const entries = buildAuthRoutes(makeAuthStub(200));
|
|
const signIn = entries.find((e) => e.route.path === "/sign-in");
|
|
const res = await signIn!.handler({
|
|
req: { raw: new Request("http://x.test/sign-in") },
|
|
} as never);
|
|
expect(res.status).toBe(200);
|
|
});
|
|
});
|