refactor(server): extract createApp and add auth/admin test seams

createApp() builds the Application without listening, so tests
can call app.handle() directly. The listen call is gated behind
import.meta.main so importing server.ts no longer boots a port.
ADMIN_DIDS is read inside requireAdmin on each call (was frozen
at module load), and a mutable authenticator indirection lets
tests stub authenticateRequest via _setAuthenticatorForTest.
This commit is contained in:
Julien Calixte
2026-06-07 22:05:12 +02:00
parent 6760c434b4
commit 8964f66184

View File

@@ -19,13 +19,25 @@ type AuthCtx = {
response: { status: number; body: unknown }; response: { status: number; body: unknown };
}; };
let authenticator: typeof authenticateRequest = authenticateRequest;
export const _setAuthenticatorForTest = (
fn: typeof authenticateRequest,
): void => {
authenticator = fn;
};
export const _resetAuthenticatorForTest = (): void => {
authenticator = authenticateRequest;
};
const requireDidOwnership = async ( const requireDidOwnership = async (
ctx: AuthCtx, ctx: AuthCtx,
did: string, did: string,
): Promise<boolean> => { ): Promise<boolean> => {
let verifiedDid: string; let verifiedDid: string;
try { try {
verifiedDid = await authenticateRequest( verifiedDid = await authenticator(
ctx.request.headers.get("Authorization"), ctx.request.headers.get("Authorization"),
); );
} catch { } catch {
@@ -41,7 +53,8 @@ const requireDidOwnership = async (
return true; return true;
}; };
const ADMIN_DIDS = new Set( const adminDids = (): Set<string> =>
new Set(
(Deno.env.get("ADMIN_DIDS") ?? "") (Deno.env.get("ADMIN_DIDS") ?? "")
.split(",") .split(",")
.map((d) => d.trim()) .map((d) => d.trim())
@@ -51,7 +64,7 @@ const ADMIN_DIDS = new Set(
const requireAdmin = async (ctx: AuthCtx): Promise<boolean> => { const requireAdmin = async (ctx: AuthCtx): Promise<boolean> => {
let verifiedDid: string; let verifiedDid: string;
try { try {
verifiedDid = await authenticateRequest( verifiedDid = await authenticator(
ctx.request.headers.get("Authorization"), ctx.request.headers.get("Authorization"),
); );
} catch { } catch {
@@ -59,7 +72,7 @@ const requireAdmin = async (ctx: AuthCtx): Promise<boolean> => {
ctx.response.body = { error: "Unauthorized" }; ctx.response.body = { error: "Unauthorized" };
return false; return false;
} }
if (!ADMIN_DIDS.has(verifiedDid)) { if (!adminDids().has(verifiedDid)) {
ctx.response.status = 403; ctx.response.status = 403;
ctx.response.body = { error: "Admin only" }; ctx.response.body = { error: "Admin only" };
return false; return false;
@@ -260,6 +273,7 @@ router.delete("/:did/webhooks/:id", async (ctx) => {
// ctx.response.status = 204; // ctx.response.status = 204;
// }) // })
export const createApp = (): Application => {
const app = new Application(); const app = new Application();
app.use(async (ctx, next) => { app.use(async (ctx, next) => {
@@ -282,5 +296,11 @@ app.use(async (ctx, next) => {
app.use(router.routes()); app.use(router.routes());
app.use(router.allowedMethods()); app.use(router.allowedMethods());
return app;
};
if (import.meta.main) {
const app = createApp();
log("[server] listening on port 8080"); log("[server] listening on port 8080");
app.listen({ port: 8080 }); app.listen({ port: 8080 });
}