Add Hydra service + migrate (todo §3); pin oryd/hydra:v26.2.0, OAuth2 issuer + login/consent URLs → our app routes

This commit is contained in:
2026-06-17 15:45:37 +02:00
parent fa87280f46
commit 93e62d8661
6 changed files with 114 additions and 3 deletions

46
src/hydra.test.ts Normal file
View File

@@ -0,0 +1,46 @@
// Guards the Ory Hydra config (§3): image pinned to an exact version (AGENTS.md),
// migrations run before the server (hydra-migrate → hydra), the DSN targets the hydra
// database, the server listens on the public/admin ports, and the issuer +
// login/consent/logout URLs point at our app. Real boot is verified by running the
// stack; this catches edits.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
const compose = read("compose.yml");
const hydraYml = read("ory/hydra/hydra.yml");
test("compose pins both hydra services to one exact version", () => {
const tags = [...compose.matchAll(/image:\s*oryd\/hydra:(\S+)/g)].map((m) => m[1]);
assert.equal(tags.length, 2, "hydra + hydra-migrate both present");
assert.equal(tags[0], tags[1], "both pinned to the same version");
const tag = tags[0]!;
assert.match(tag, /^v\d+\.\d+\.\d+$/, `${tag} is an exact vMAJOR.MINOR.PATCH`);
assert.doesNotMatch(tag, /latest|[\^~*]/, `${tag} is exact, not floating`);
});
test("hydra migrations run once before the server starts", () => {
assert.ok((compose.match(/migrate sql -e --yes/g) ?? []).length >= 2,
"hydra-migrate runs SQL migrations (alongside kratos)");
assert.ok((compose.match(/condition:\s*service_completed_successfully/g) ?? []).length >= 3,
"hydra waits for hydra-migrate (alongside kratos's + keto's gates)");
});
test("hydra DSN targets the per-service hydra database", () => {
const dsns = [...compose.matchAll(/DSN:\s*(\S+)/g)].map((m) => m[1]).filter((d) => /\/hydra\b/.test(d!));
assert.ok(dsns.length >= 2, "both hydra services point DSN at the hydra DB");
for (const dsn of dsns) assert.match(dsn!, /@postgres:5432\/hydra\b/, `${dsn} hits the hydra DB`);
});
test("hydra serves OAuth2 on the public + admin ports", () => {
assert.match(hydraYml, /public:\s*\n\s*port:\s*4444/, "public API on 4444");
assert.match(hydraYml, /admin:\s*\n\s*port:\s*4445/, "admin API on 4445");
});
test("hydra issuer + login/consent/logout URLs point at our app", () => {
assert.match(hydraYml, /issuer:\s*http:\/\/127\.0\.0\.1:4444\//, "issuer is the public OAuth2 URL");
assert.match(hydraYml, /login:\s*http:\/\/127\.0\.0\.1:3000\/oauth2\/login\b/, "login challenge → our /oauth2/login");
assert.match(hydraYml, /consent:\s*http:\/\/127\.0\.0\.1:3000\/oauth2\/consent\b/, "consent challenge → our /oauth2/consent");
assert.match(hydraYml, /logout:\s*http:\/\/127\.0\.0\.1:3000\/oauth2\/logout\b/, "logout → our /oauth2/logout");
});