Heimdall/tests/app.test.ts
2026-04-26 12:25:11 +01:00

162 lines
4.7 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import { type FastifyInstance } from "fastify";
import { buildApp } from "../src/app.js";
import { type HeimdallConfig } from "../src/config.js";
import { decodeJwt, verifyJwt } from "../src/signing.js";
function createTestConfig(): HeimdallConfig {
return {
serviceName: "heimdall",
host: "127.0.0.1",
port: 4100,
publicBaseUrl: "https://heimdall.gamecult.org",
issuer: "https://heimdall.gamecult.org",
sessionTtlSeconds: 3600,
stateTtlSeconds: 600,
providers: {
discord: { clientId: "discord-client", clientSecret: "discord-secret" },
patreon: { clientId: "patreon-client", clientSecret: "patreon-secret" },
github: { clientId: "github-client", clientSecret: "github-secret" },
twitch: { clientId: "twitch-client", clientSecret: "twitch-secret" },
youtube: { clientId: "youtube-client", clientSecret: "youtube-secret" },
},
};
}
const apps: Awaited<ReturnType<typeof buildApp>>[] = [];
function getPublicKey(app: FastifyInstance) {
return (
app as FastifyInstance & {
heimdallContext: {
keys: {
publicKey: Parameters<typeof verifyJwt>[1];
};
};
}
).heimdallContext.keys.publicKey;
}
afterEach(async () => {
while (apps.length) {
await apps.pop()?.close();
}
});
describe("Heimdall service skeleton", () => {
it("serves discovery and JWKS metadata", async () => {
const app = await buildApp({ config: createTestConfig() });
apps.push(app);
const discoveryResponse = await app.inject({
method: "GET",
url: "/.well-known/heimdall-configuration",
});
expect(discoveryResponse.statusCode).toBe(200);
expect(discoveryResponse.json()).toEqual(
expect.objectContaining({
issuer: "https://heimdall.gamecult.org",
jwksUri: "https://heimdall.gamecult.org/.well-known/jwks.json",
})
);
const jwksResponse = await app.inject({
method: "GET",
url: "/.well-known/jwks.json",
});
expect(jwksResponse.statusCode).toBe(200);
expect(jwksResponse.json()).toEqual(
expect.objectContaining({
keys: [
expect.objectContaining({
alg: "EdDSA",
kid: expect.any(String),
use: "sig",
}),
],
})
);
});
it("builds signed OAuth start state for supported providers", async () => {
const app = await buildApp({ config: createTestConfig() });
apps.push(app);
const response = await app.inject({
method: "POST",
url: "/v1/oauth/discord/start",
payload: {
appSlug: "repixelizer",
mode: "sign_in",
returnTo: "https://repixelizer.gamecult.org/app/",
},
});
expect(response.statusCode).toBe(201);
const payload = response.json();
expect(payload.authorizationUrl).toContain("discord.com/oauth2/authorize");
const decoded = decodeJwt(payload.stateToken);
expect(decoded.payload).toEqual(
expect.objectContaining({
typ: "heimdall_oauth_state",
provider: "discord",
app_slug: "repixelizer",
})
);
});
it("issues signed claims and evaluates shared app capabilities", async () => {
const app = await buildApp({ config: createTestConfig() });
apps.push(app);
const response = await app.inject({
method: "POST",
url: "/v1/apps/repixelizer/claims/issue",
payload: {
accountId: "acct_repixelizer_001",
displayName: "Meta",
facts: ["discord.allowed_role", "grant.operator"],
linkedIdentities: [
{
provider: "discord",
providerUserId: "123456789",
username: "meta",
},
],
},
});
expect(response.statusCode).toBe(201);
const payload = response.json();
expect(payload.sharedCapabilities).toEqual(
expect.arrayContaining(["app_access", "queue_submit", "admin_access"])
);
const verified = verifyJwt(payload.accessToken, getPublicKey(app));
expect(verified.valid).toBe(true);
const tokenDecoded = decodeJwt(payload.accessToken);
expect(tokenDecoded.payload.capabilities).toEqual(
expect.arrayContaining(["app_access", "queue_submit", "admin_access"])
);
});
it("exposes StreamPixels hybrid capability seams without granting them blindly", async () => {
const app = await buildApp({ config: createTestConfig() });
apps.push(app);
const profileResponse = await app.inject({
method: "GET",
url: "/v1/apps/streampixels",
});
expect(profileResponse.statusCode).toBe(200);
expect(profileResponse.json()).toEqual(
expect.objectContaining({
slug: "streampixels",
managedConnectionProviders: ["twitch", "youtube"],
})
);
});
});