63 lines
3.2 KiB
TypeScript
63 lines
3.2 KiB
TypeScript
// Guards the Ory Keto config (§3): image pinned to an exact version (AGENTS.md),
|
|
// migrations run before the server (keto-migrate → keto), the DSN targets the keto
|
|
// database, read/write APIs serve on the ports config.ts points at, and the OPL
|
|
// declares the role/group/resource namespaces. 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 ketoYml = read("ory/keto/keto.yml");
|
|
const opl = read("ory/keto/namespaces.keto.ts");
|
|
|
|
test("compose pins both keto services to one exact version", () => {
|
|
const tags = [...compose.matchAll(/image:\s*oryd\/keto:(\S+)/g)].map((m) => m[1]);
|
|
assert.equal(tags.length, 2, "keto + keto-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("keto migrations run once before the server starts", () => {
|
|
assert.match(compose, /migrate\s+up\s+-y/, "keto-migrate runs migrations");
|
|
assert.ok((compose.match(/condition:\s*service_completed_successfully/g) ?? []).length >= 2,
|
|
"keto waits for keto-migrate (alongside kratos's gate)");
|
|
});
|
|
|
|
test("keto DSN targets the per-service keto database", () => {
|
|
const dsns = [...compose.matchAll(/DSN:\s*(\S+)/g)].map((m) => m[1]);
|
|
const ketoDsns = dsns.filter((d) => /\/keto\b/.test(d!));
|
|
assert.ok(ketoDsns.length >= 2, "both keto services point DSN at the keto DB");
|
|
for (const dsn of ketoDsns) assert.match(dsn!, /@postgres:5432\/keto\b/, `${dsn} hits the keto DB`);
|
|
});
|
|
|
|
test("keto serves read/write on the ports config.ts targets", () => {
|
|
// config.ts defaults: ketoReadUrl=http://keto:4466, ketoWriteUrl=http://keto:4467.
|
|
assert.match(ketoYml, /read:\s*\n\s*host:[^\n]*\n\s*port:\s*4466/, "read API on 4466");
|
|
assert.match(ketoYml, /write:\s*\n\s*host:[^\n]*\n\s*port:\s*4467/, "write API on 4467");
|
|
});
|
|
|
|
test("keto loads the OPL namespaces from the mounted file", () => {
|
|
assert.match(ketoYml, /location:\s*file:\/\/\/etc\/config\/keto\/namespaces\.keto\.ts/,
|
|
"namespaces come from the committed OPL");
|
|
});
|
|
|
|
test("the OPL declares role, group and a resource namespace over user subjects", () => {
|
|
for (const ns of ["User", "Group", "Role", "Resource"])
|
|
assert.match(opl, new RegExp(`class ${ns} implements Namespace`), `defines ${ns}`);
|
|
// role + group are subject sets read at login → JWT roles claim (README).
|
|
assert.match(opl, /class Role implements Namespace\s*{\s*related:\s*{\s*members:/,
|
|
"Role has a members relation");
|
|
assert.match(opl, /class Group implements Namespace\s*{\s*related:\s*{\s*members:/,
|
|
"Group has a members relation");
|
|
});
|
|
|
|
test("the resource namespace exposes fine-grained permissions (permits)", () => {
|
|
// README's third tier: the rare live Keto check. owners ⊇ editors ⊇ viewers.
|
|
assert.match(opl, /class Resource[\s\S]*permits\s*=\s*{[\s\S]*view:[\s\S]*edit:[\s\S]*delete:/,
|
|
"Resource permits view/edit/delete");
|
|
});
|