@nxgt/janus 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conformance/assert.d.ts +5 -1
- package/dist/conformance/assert.d.ts.map +1 -1
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/users.d.ts.map +1 -1
- package/dist/conformance/index.js +11 -11
- package/dist/conformance/index.js.map +6 -6
- package/dist/conformance/relations.d.ts.map +1 -1
- package/docs/troubleshooting.md +23 -2
- package/package.json +1 -1
|
@@ -31,6 +31,10 @@ export declare function rejects(promise: Promise<unknown>, what: string): Promis
|
|
|
31
31
|
*
|
|
32
32
|
* The probe says what breaks; `verify:artifacts` says whether a duplicate is
|
|
33
33
|
* present. Both are needed.
|
|
34
|
+
*
|
|
35
|
+
* `name` is the error's own `name` field, passed rather than read from
|
|
36
|
+
* `cls.name`: a bundler renames a class that collides — `StoreFailure2` in
|
|
37
|
+
* `dist` — and the probe would then never recognise the duplicate.
|
|
34
38
|
*/
|
|
35
|
-
export declare function isOurs<T>(error: unknown, cls: abstract new (...args: never[]) => T, what: string): asserts error is T;
|
|
39
|
+
export declare function isOurs<T>(error: unknown, cls: abstract new (...args: never[]) => T, name: string, what: string): asserts error is T;
|
|
36
40
|
//# sourceMappingURL=assert.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/conformance/assert.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uFAAuF;AACvF,wBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAO5E;AAED,iGAAiG;AACjG,wBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM1D;AAED,sCAAsC;AACtC,wBAAgB,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACtB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EACzB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED
|
|
1
|
+
{"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/conformance/assert.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uFAAuF;AACvF,wBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAO5E;AAED,iGAAiG;AACjG,wBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM1D;AAED,sCAAsC;AACtC,wBAAgB,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACtB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EACzB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,CAAC,EACvB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EACzC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,KAAK,IAAI,CAAC,CAWpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/outage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,eAAe,EAAc,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/outage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,eAAe,EAAc,MAAM,UAAU,CAAC;AAiEzE,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EAqFjD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/users.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAYhD,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/users.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAYhD,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EAkiBpD,CAAC"}
|
|
@@ -37,15 +37,15 @@ function rejects(promise, what) {
|
|
|
37
37
|
expected a rejection, got ${show(answer)}`);
|
|
38
38
|
}, (error) => error);
|
|
39
39
|
}
|
|
40
|
-
function isOurs(error, cls, what) {
|
|
40
|
+
function isOurs(error, cls, name, what) {
|
|
41
41
|
if (error instanceof cls)
|
|
42
42
|
return;
|
|
43
|
-
if (error?.name ===
|
|
43
|
+
if (error?.name === name) {
|
|
44
44
|
throw new Error(`${what}
|
|
45
|
-
the error is named ${
|
|
45
|
+
the error is named ${name} but is not @nxgt/janus's ${name}: two copies of @nxgt/janus are installed. The adapter must list it as a peer dependency, never a dependency`);
|
|
46
46
|
}
|
|
47
47
|
throw new Error(`${what}
|
|
48
|
-
expected ${
|
|
48
|
+
expected ${name}, got ${show(error)}`);
|
|
49
49
|
}
|
|
50
50
|
function differ(actual, expected, path) {
|
|
51
51
|
if (expected instanceof Date || actual instanceof Date) {
|
|
@@ -163,7 +163,7 @@ function outage(slot, method, call, seed) {
|
|
|
163
163
|
await context.faults?.fail(slot, method);
|
|
164
164
|
const error = await rejects(call(context), `${slot}.${method} under an outage should reject: answering an absence turns an outage into a silent lockout`);
|
|
165
165
|
if (error instanceof JanusError2 || error?.name === "StoreFailure") {
|
|
166
|
-
isOurs(error, StoreFailure2, `${slot}.${method} under an outage`);
|
|
166
|
+
isOurs(error, StoreFailure2, "StoreFailure", `${slot}.${method} under an outage`);
|
|
167
167
|
}
|
|
168
168
|
ok(error?.code !== "NOT_FOUND", `${slot}.${method} under an outage rejected with NOT_FOUND: an outage is never an absence`);
|
|
169
169
|
}
|
|
@@ -688,7 +688,7 @@ var userStoreCases = [
|
|
|
688
688
|
const refused = outcomes.flatMap((o) => o.status === "rejected" ? [o.reason] : []);
|
|
689
689
|
equal(accepted.length, 1, "insertUser: of twenty concurrent inserts of one login, exactly one should be accepted — a read followed by a write lets several through");
|
|
690
690
|
for (const error of refused) {
|
|
691
|
-
isOurs(error, StoreConflict2, "insertUser should refuse a taken login with StoreConflict");
|
|
691
|
+
isOurs(error, StoreConflict2, "StoreConflict", "insertUser should refuse a taken login with StoreConflict");
|
|
692
692
|
equal(error.code, "LOGIN_TAKEN", "insertUser: the conflict code");
|
|
693
693
|
equal(error.login, "contested@example.test", "insertUser: the conflict should name the login");
|
|
694
694
|
ok(!error.message.includes("contested@example.test"), `insertUser: the conflict's message should not quote the login — a message never carries a value; got ${JSON.stringify(error.message)}`);
|
|
@@ -733,7 +733,7 @@ var userStoreCases = [
|
|
|
733
733
|
active: true,
|
|
734
734
|
fields: { email: "overwritten@example.test" }
|
|
735
735
|
}, 0), "updateUser at a stale version should reject");
|
|
736
|
-
isOurs(error, StoreConflict2, "updateUser at a stale version should reject with StoreConflict");
|
|
736
|
+
isOurs(error, StoreConflict2, "StoreConflict", "updateUser at a stale version should reject with StoreConflict");
|
|
737
737
|
equal(error.code, "VERSION_CONFLICT", "updateUser: the conflict code");
|
|
738
738
|
equal(error.expectedVersion, 0, "updateUser: expectedVersion");
|
|
739
739
|
equal(error.actualVersion, 1, "updateUser: actualVersion");
|
|
@@ -746,7 +746,7 @@ var userStoreCases = [
|
|
|
746
746
|
name: "refuses an update to an unknown id with NotFoundError, told apart from a version conflict",
|
|
747
747
|
async run({ stores }) {
|
|
748
748
|
const error = await rejects(stores.users.updateUser(mintId2(), { updatedAt: at("2026-02-01T00:00:00.000Z") }, 0), "updateUser on an unknown id should reject");
|
|
749
|
-
isOurs(error, NotFoundError2, "updateUser on an unknown id should reject with NotFoundError, not a version conflict");
|
|
749
|
+
isOurs(error, NotFoundError2, "NotFoundError", "updateUser on an unknown id should reject with NotFoundError, not a version conflict");
|
|
750
750
|
}
|
|
751
751
|
},
|
|
752
752
|
{
|
|
@@ -822,7 +822,7 @@ var userStoreCases = [
|
|
|
822
822
|
isNull(await stores.users.findUserByLogin("user", old ?? ""), "findUserByLogin for a login the update replaced");
|
|
823
823
|
equal((await stores.users.findUserByLogin("user", moved))?.id, record.id, "findUserByLogin for the login the update wrote");
|
|
824
824
|
const error = await rejects(stores.users.updateUser(other.id, { updatedAt: at("2026-02-02T00:00:00.000Z"), logins: [moved] }, 0), "updateUser onto a login another user holds should reject");
|
|
825
|
-
isOurs(error, StoreConflict2, "updateUser onto a held login should reject with StoreConflict");
|
|
825
|
+
isOurs(error, StoreConflict2, "StoreConflict", "updateUser onto a held login should reject with StoreConflict");
|
|
826
826
|
equal(error.code, "LOGIN_TAKEN", "updateUser: the conflict code");
|
|
827
827
|
equal(await stores.users.findUser(other.id), other, "updateUser refused for a login should have written nothing");
|
|
828
828
|
}
|
|
@@ -1243,7 +1243,7 @@ function outage2(method, call, after) {
|
|
|
1243
1243
|
await faults?.fail(method);
|
|
1244
1244
|
const error = await rejects(call(store), `relations.${method} under an outage should reject`);
|
|
1245
1245
|
if (error instanceof JanusError2 || error?.name === "StoreFailure") {
|
|
1246
|
-
isOurs(error, StoreFailure2, `relations.${method} under an outage`);
|
|
1246
|
+
isOurs(error, StoreFailure2, "StoreFailure", `relations.${method} under an outage`);
|
|
1247
1247
|
}
|
|
1248
1248
|
ok(error?.code !== "NOT_FOUND", `relations.${method} under an outage rejected with NOT_FOUND: an outage is never an absence`);
|
|
1249
1249
|
await after?.(store);
|
|
@@ -1343,5 +1343,5 @@ export {
|
|
|
1343
1343
|
userStoreCases
|
|
1344
1344
|
};
|
|
1345
1345
|
|
|
1346
|
-
//# debugId=
|
|
1346
|
+
//# debugId=7779A65B64EF7C9A64756E2164756E21
|
|
1347
1347
|
//# sourceMappingURL=index.js.map
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/conformance/assert.ts", "../src/conformance/fixtures.ts", "../src/conformance/cases/outage.ts", "../src/conformance/cases/sessions.ts", "../src/conformance/cases/tokens.ts", "../src/conformance/cases/users.ts", "../src/conformance/describe.ts", "../src/conformance/reference.ts", "../src/conformance/relations.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * The suite's five assertions.\n *\n * **No assertion library, no test framework**: `bun:test` in the dependency\n * graph of a published library is a dependency every consumer installs, and an\n * adapter author on vitest could not run the suite at all. Each throws a plain\n * `Error` whose message names the port method and what it should have answered\n * — the sentence an adapter author reads first.\n */\n\n/** Fails unless `actual` deep-equals `expected`: dates by instant, `1` never `'1'`. */\nexport function equal(actual: unknown, expected: unknown, what: string): void {\n\tconst difference = differ(actual, expected, '');\n\tif (difference !== null) {\n\t\tthrow new Error(\n\t\t\t`${what}\\n at ${difference.path || '(root)'}: expected ${show(difference.expected)}, got ${show(difference.actual)}`,\n\t\t);\n\t}\n}\n\n/** Fails unless `actual` is `null` — not `undefined`, which is a store that forgot to answer. */\nexport function isNull(actual: unknown, what: string): void {\n\tif (actual !== null) {\n\t\tthrow new Error(\n\t\t\t`${what}\\n expected null, got ${actual === undefined ? 'undefined — an absence is null; undefined is a store that forgot to answer' : show(actual)}`,\n\t\t);\n\t}\n}\n\n/** Fails unless `condition` holds. */\nexport function ok(condition: boolean, what: string): void {\n\tif (!condition) throw new Error(what);\n}\n\n/**\n * The rejection of `promise`, or a failure when it resolves.\n *\n * **Settled where it is created**, with `.then(ok, ko)`: a rejection awaited\n * too late is counted unhandled by Bun and fails the case with the very error\n * it was checking — measured in nxgt-data, where a loaded CI runner failed\n * where an idle laptop passed.\n */\nexport function rejects(\n\tpromise: Promise<unknown>,\n\twhat: string,\n): Promise<unknown> {\n\treturn promise.then(\n\t\t(answer) => {\n\t\t\tthrow new Error(`${what}\\n expected a rejection, got ${show(answer)}`);\n\t\t},\n\t\t(error: unknown) => error,\n\t);\n}\n\n/**\n * The `instanceof` probe: a class that is named like this package's but is not\n * this package's class means **two copies of `@nxgt/janus`** are installed —\n * the adapter depends on it instead of peering it. Then `StoreConflict` stops\n * passing through the core, and a login that is taken is answered as an\n * outage.\n *\n * The probe says what breaks; `verify:artifacts` says whether a duplicate is\n * present. Both are needed.\n */\nexport function isOurs<T>(\n\terror: unknown,\n\tcls: abstract new (...args: never[]) => T,\n\twhat: string,\n): asserts error is T {\n\tif (error instanceof cls) return;\n\n\t// Same name, not the same class: the duplicate the probe exists for.\n\tif ((error as { name?: unknown } | null)?.name ===
|
|
5
|
+
"/**\n * The suite's five assertions.\n *\n * **No assertion library, no test framework**: `bun:test` in the dependency\n * graph of a published library is a dependency every consumer installs, and an\n * adapter author on vitest could not run the suite at all. Each throws a plain\n * `Error` whose message names the port method and what it should have answered\n * — the sentence an adapter author reads first.\n */\n\n/** Fails unless `actual` deep-equals `expected`: dates by instant, `1` never `'1'`. */\nexport function equal(actual: unknown, expected: unknown, what: string): void {\n\tconst difference = differ(actual, expected, '');\n\tif (difference !== null) {\n\t\tthrow new Error(\n\t\t\t`${what}\\n at ${difference.path || '(root)'}: expected ${show(difference.expected)}, got ${show(difference.actual)}`,\n\t\t);\n\t}\n}\n\n/** Fails unless `actual` is `null` — not `undefined`, which is a store that forgot to answer. */\nexport function isNull(actual: unknown, what: string): void {\n\tif (actual !== null) {\n\t\tthrow new Error(\n\t\t\t`${what}\\n expected null, got ${actual === undefined ? 'undefined — an absence is null; undefined is a store that forgot to answer' : show(actual)}`,\n\t\t);\n\t}\n}\n\n/** Fails unless `condition` holds. */\nexport function ok(condition: boolean, what: string): void {\n\tif (!condition) throw new Error(what);\n}\n\n/**\n * The rejection of `promise`, or a failure when it resolves.\n *\n * **Settled where it is created**, with `.then(ok, ko)`: a rejection awaited\n * too late is counted unhandled by Bun and fails the case with the very error\n * it was checking — measured in nxgt-data, where a loaded CI runner failed\n * where an idle laptop passed.\n */\nexport function rejects(\n\tpromise: Promise<unknown>,\n\twhat: string,\n): Promise<unknown> {\n\treturn promise.then(\n\t\t(answer) => {\n\t\t\tthrow new Error(`${what}\\n expected a rejection, got ${show(answer)}`);\n\t\t},\n\t\t(error: unknown) => error,\n\t);\n}\n\n/**\n * The `instanceof` probe: a class that is named like this package's but is not\n * this package's class means **two copies of `@nxgt/janus`** are installed —\n * the adapter depends on it instead of peering it. Then `StoreConflict` stops\n * passing through the core, and a login that is taken is answered as an\n * outage.\n *\n * The probe says what breaks; `verify:artifacts` says whether a duplicate is\n * present. Both are needed.\n *\n * `name` is the error's own `name` field, passed rather than read from\n * `cls.name`: a bundler renames a class that collides — `StoreFailure2` in\n * `dist` — and the probe would then never recognise the duplicate.\n */\nexport function isOurs<T>(\n\terror: unknown,\n\tcls: abstract new (...args: never[]) => T,\n\tname: string,\n\twhat: string,\n): asserts error is T {\n\tif (error instanceof cls) return;\n\n\t// Same name, not the same class: the duplicate the probe exists for.\n\tif ((error as { name?: unknown } | null)?.name === name) {\n\t\tthrow new Error(\n\t\t\t`${what}\\n the error is named ${name} but is not @nxgt/janus's ${name}: two copies of @nxgt/janus are installed. The adapter must list it as a peer dependency, never a dependency`,\n\t\t);\n\t}\n\n\tthrow new Error(`${what}\\n expected ${name}, got ${show(error)}`);\n}\n\ninterface Difference {\n\treadonly path: string;\n\treadonly expected: unknown;\n\treadonly actual: unknown;\n}\n\nfunction differ(\n\tactual: unknown,\n\texpected: unknown,\n\tpath: string,\n): Difference | null {\n\tif (expected instanceof Date || actual instanceof Date) {\n\t\treturn actual instanceof Date &&\n\t\t\texpected instanceof Date &&\n\t\t\tactual.getTime() === expected.getTime()\n\t\t\t? null\n\t\t\t: { path, expected, actual };\n\t}\n\n\tif (Array.isArray(expected) || Array.isArray(actual)) {\n\t\tif (!Array.isArray(expected) || !Array.isArray(actual)) {\n\t\t\treturn { path, expected, actual };\n\t\t}\n\t\tif (expected.length !== actual.length) {\n\t\t\treturn {\n\t\t\t\tpath: `${path}.length`,\n\t\t\t\texpected: expected.length,\n\t\t\t\tactual: actual.length,\n\t\t\t};\n\t\t}\n\t\tfor (let i = 0; i < expected.length; i += 1) {\n\t\t\tconst inner = differ(actual[i], expected[i], `${path}[${i}]`);\n\t\t\tif (inner !== null) return inner;\n\t\t}\n\t\treturn null;\n\t}\n\n\tif (\n\t\ttypeof expected === 'object' &&\n\t\texpected !== null &&\n\t\ttypeof actual === 'object' &&\n\t\tactual !== null\n\t) {\n\t\tconst keys = new Set([...Object.keys(expected), ...Object.keys(actual)]);\n\t\tfor (const key of keys) {\n\t\t\tconst inner = differ(\n\t\t\t\t(actual as Record<string, unknown>)[key],\n\t\t\t\t(expected as Record<string, unknown>)[key],\n\t\t\t\tpath === '' ? key : `${path}.${key}`,\n\t\t\t);\n\t\t\tif (inner !== null) return inner;\n\t\t}\n\t\treturn null;\n\t}\n\n\treturn Object.is(actual, expected) ? null : { path, expected, actual };\n}\n\nfunction show(value: unknown): string {\n\tif (value instanceof Error) return `${value.name}: ${value.message}`;\n\tif (value instanceof Date) return value.toISOString();\n\tif (value === undefined) return 'undefined';\n\tif (typeof value === 'string') return JSON.stringify(value);\n\ttry {\n\t\tconst json = JSON.stringify(value);\n\t\treturn json.length > 200 ? `${json.slice(0, 200)}…` : json;\n\t} catch {\n\t\treturn String(value);\n\t}\n}\n",
|
|
6
6
|
"import type {\n\tSessionRecord,\n\tTokenRecord,\n\tUserRecord,\n} from '../auth/port/types';\nimport { mintId } from '../ids/id';\n\n/**\n * Records the cases write. Every id is minted, so no two cases — and no two\n * records of one case — ever collide by accident; every collision a case\n * tests is one it wrote on purpose.\n */\n\nexport const at = (iso: string): Date => new Date(iso);\n\n/** A fresh login, so each record holds its own unless a case says otherwise. */\nconst fresh = () => `user-${mintId()}@example.test`;\n\nexport function userRecord(overrides: Partial<UserRecord> = {}): UserRecord {\n\tconst email = fresh();\n\treturn {\n\t\tid: mintId(),\n\t\ttype: 'user',\n\t\tschemaVersion: '1',\n\t\tactive: true,\n\t\tfields: { email, name: { first: 'Ada', last: 'Lovelace' } },\n\t\tlogins: [email],\n\t\tpassword: {\n\t\t\thash: '$scrypt$ln=17,r=8,p=1$c2FsdA==$a2V5',\n\t\t\tupdatedAt: at('2026-01-01T00:00:00.000Z'),\n\t\t},\n\t\tsecondFactor: null,\n\t\temailVerifiedAt: null,\n\t\tversion: 0,\n\t\tcreatedAt: at('2026-01-01T00:00:00.000Z'),\n\t\tupdatedAt: at('2026-01-01T00:00:00.000Z'),\n\t\t...overrides,\n\t};\n}\n\nexport function sessionRecord(\n\toverrides: Partial<SessionRecord> = {},\n): SessionRecord {\n\treturn {\n\t\tid: mintId(),\n\t\ttokenHash: hexOf(mintId()),\n\t\tuserId: mintId(),\n\t\tauthenticatedAt: at('2026-01-01T00:00:00.000Z'),\n\t\texpiresAt: at('2099-01-01T00:00:00.000Z'),\n\t\trevokedAt: null,\n\t\tcreatedAt: at('2026-01-01T00:00:00.000Z'),\n\t\t...overrides,\n\t};\n}\n\nexport function tokenRecord(overrides: Partial<TokenRecord> = {}): TokenRecord {\n\treturn {\n\t\ttokenHash: hexOf(mintId()),\n\t\tkind: 'resetPassword',\n\t\tuserId: mintId(),\n\t\taddress: fresh(),\n\t\tcodeHash: null,\n\t\tattempts: 0,\n\t\texpiresAt: at('2099-01-01T00:00:00.000Z'),\n\t\tspentAt: null,\n\t\tcreatedAt: at('2026-01-01T00:00:00.000Z'),\n\t\t...overrides,\n\t};\n}\n\n/** A 64-character hex string, shaped like a `sha256`, unique per call. */\nfunction hexOf(seed: string): string {\n\treturn seed.replace(/-/g, '').padEnd(64, '0').slice(0, 64);\n}\n",
|
|
7
|
-
"import type { JanusStores } from '../../auth/port/types';\nimport { JanusError, StoreFailure } from '../../errors/janus-error';\nimport { isOurs, ok, rejects } from '../assert';\nimport { at, sessionRecord, tokenRecord, userRecord } from '../fixtures';\nimport type { CaseContext, ConformanceCase, PortMethod } from '../types';\n\n/**\n * **The invariant, as cases.** For each of the thirteen methods whose honest\n * answer can be \"nothing\" — `null`, `false`, `0`, an empty page — a store that\n * cannot answer must **reject**, and the rejection must not be `NOT_FOUND`.\n *\n * Every case seeds a record first, so a store that swallows its failure into an\n * absence answers something *false* rather than something merely empty.\n *\n * The rejection is either `@nxgt/janus`'s own `StoreFailure` — the `instanceof`\n * probe, which fails on a second copy of the package — or an error that is no\n * `JanusError` at all, which the core wraps into `STORE_FAILED` itself.\n */\nfunction outage<S extends keyof JanusStores>(\n\tslot: S,\n\tmethod: PortMethod<S>,\n\tcall: (context: CaseContext) => Promise<unknown>,\n\tseed: (context: CaseContext) => Promise<void>,\n): ConformanceCase {\n\treturn {\n\t\tid: `outage.${method}`,\n\t\tgroup: 'outage',\n\t\tname: `${slot}.${method} rejects when the store cannot answer — never null, false, 0 or an empty page`,\n\t\tneeds: 'faults',\n\t\tasync run(context) {\n\t\t\tawait seed(context);\n\t\t\tawait context.faults?.fail(slot, method);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tcall(context),\n\t\t\t\t`${slot}.${method} under an outage should reject: answering an absence turns an outage into a silent lockout`,\n\t\t\t);\n\n\t\t\tif (\n\t\t\t\terror instanceof JanusError ||\n\t\t\t\t(error as { name?: unknown })?.name === 'StoreFailure'\n\t\t\t) {\n\t\t\t\tisOurs(
|
|
7
|
+
"import type { JanusStores } from '../../auth/port/types';\nimport { JanusError, StoreFailure } from '../../errors/janus-error';\nimport { isOurs, ok, rejects } from '../assert';\nimport { at, sessionRecord, tokenRecord, userRecord } from '../fixtures';\nimport type { CaseContext, ConformanceCase, PortMethod } from '../types';\n\n/**\n * **The invariant, as cases.** For each of the thirteen methods whose honest\n * answer can be \"nothing\" — `null`, `false`, `0`, an empty page — a store that\n * cannot answer must **reject**, and the rejection must not be `NOT_FOUND`.\n *\n * Every case seeds a record first, so a store that swallows its failure into an\n * absence answers something *false* rather than something merely empty.\n *\n * The rejection is either `@nxgt/janus`'s own `StoreFailure` — the `instanceof`\n * probe, which fails on a second copy of the package — or an error that is no\n * `JanusError` at all, which the core wraps into `STORE_FAILED` itself.\n */\nfunction outage<S extends keyof JanusStores>(\n\tslot: S,\n\tmethod: PortMethod<S>,\n\tcall: (context: CaseContext) => Promise<unknown>,\n\tseed: (context: CaseContext) => Promise<void>,\n): ConformanceCase {\n\treturn {\n\t\tid: `outage.${method}`,\n\t\tgroup: 'outage',\n\t\tname: `${slot}.${method} rejects when the store cannot answer — never null, false, 0 or an empty page`,\n\t\tneeds: 'faults',\n\t\tasync run(context) {\n\t\t\tawait seed(context);\n\t\t\tawait context.faults?.fail(slot, method);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tcall(context),\n\t\t\t\t`${slot}.${method} under an outage should reject: answering an absence turns an outage into a silent lockout`,\n\t\t\t);\n\n\t\t\tif (\n\t\t\t\terror instanceof JanusError ||\n\t\t\t\t(error as { name?: unknown })?.name === 'StoreFailure'\n\t\t\t) {\n\t\t\t\tisOurs(\n\t\t\t\t\terror,\n\t\t\t\t\tStoreFailure,\n\t\t\t\t\t'StoreFailure',\n\t\t\t\t\t`${slot}.${method} under an outage`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tok(\n\t\t\t\t(error as { code?: unknown } | null)?.code !== 'NOT_FOUND',\n\t\t\t\t`${slot}.${method} under an outage rejected with NOT_FOUND: an outage is never an absence`,\n\t\t\t);\n\t\t},\n\t};\n}\n\nconst user = userRecord();\nconst session = sessionRecord({ userId: user.id });\nconst token = tokenRecord({ userId: user.id });\nconst now = at('2026-02-01T00:00:00.000Z');\n\n/** Each case writes the same three records, fresh stores every time. */\nconst seed = async ({ stores }: CaseContext) => {\n\tawait stores.users.insertUser(user);\n\tawait stores.sessions.insertSession(session);\n\tawait stores.tokens.insertToken(token);\n};\n\nexport const outageCases: readonly ConformanceCase[] = [\n\toutage(\n\t\t'users',\n\t\t'findUser',\n\t\t({ stores }) => stores.users.findUser(user.id),\n\t\tseed,\n\t),\n\toutage(\n\t\t'users',\n\t\t'findUserByLogin',\n\t\t({ stores }) =>\n\t\t\tstores.users.findUserByLogin(user.type, user.logins[0] ?? ''),\n\t\tseed,\n\t),\n\toutage(\n\t\t'users',\n\t\t'listUsers',\n\t\t({ stores }) =>\n\t\t\tstores.users.listUsers({ type: user.type, after: null, limit: 10 }),\n\t\tseed,\n\t),\n\toutage(\n\t\t'sessions',\n\t\t'findSessionByTokenHash',\n\t\t({ stores }) => stores.sessions.findSessionByTokenHash(session.tokenHash),\n\t\tseed,\n\t),\n\toutage(\n\t\t'sessions',\n\t\t'extendSession',\n\t\t({ stores }) =>\n\t\t\tstores.sessions.extendSession(session.id, at('2099-06-01T00:00:00.000Z')),\n\t\tseed,\n\t),\n\toutage(\n\t\t'sessions',\n\t\t'revokeSession',\n\t\t({ stores }) => stores.sessions.revokeSession(session.id, now),\n\t\tseed,\n\t),\n\toutage(\n\t\t'sessions',\n\t\t'revokeUserSessions',\n\t\t({ stores }) => stores.sessions.revokeUserSessions(user.id, now),\n\t\tseed,\n\t),\n\toutage(\n\t\t'tokens',\n\t\t'consumeToken',\n\t\t({ stores }) =>\n\t\t\tstores.tokens.consumeToken(token.tokenHash, 'resetPassword', now),\n\t\tseed,\n\t),\n\toutage(\n\t\t'tokens',\n\t\t'countAttempt',\n\t\t({ stores }) =>\n\t\t\tstores.tokens.countAttempt(token.tokenHash, 'resetPassword'),\n\t\tseed,\n\t),\n\toutage(\n\t\t'tokens',\n\t\t'spendUserTokens',\n\t\t({ stores }) =>\n\t\t\tstores.tokens.spendUserTokens(user.id, 'resetPassword', now),\n\t\tseed,\n\t),\n\toutage(\n\t\t'users',\n\t\t'deleteUser',\n\t\t({ stores }) => stores.users.deleteUser(user.id),\n\t\tseed,\n\t),\n\toutage(\n\t\t'sessions',\n\t\t'deleteUserSessions',\n\t\t({ stores }) => stores.sessions.deleteUserSessions(user.id),\n\t\tseed,\n\t),\n\toutage(\n\t\t'tokens',\n\t\t'deleteUserTokens',\n\t\t({ stores }) => stores.tokens.deleteUserTokens(user.id),\n\t\tseed,\n\t),\n];\n",
|
|
8
8
|
"import { mintId } from '../../ids/id';\nimport { equal, isNull, ok } from '../assert';\nimport { at, sessionRecord } from '../fixtures';\nimport type { ConformanceCase } from '../types';\n\nconst group = 'sessions';\n\nexport const sessionStoreCases: readonly ConformanceCase[] = [\n\t{\n\t\tid: 'sessions.verbatim',\n\t\tgroup,\n\t\tname: 'answers a stored session verbatim — revoked or lapsed — or null once lapsed, and never a changed record',\n\t\tasync run({ stores }) {\n\t\t\tconst standing = sessionRecord();\n\t\t\tconst revoked = sessionRecord({\n\t\t\t\trevokedAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\t\t\tconst lapsed = sessionRecord({\n\t\t\t\texpiresAt: at('2020-01-01T00:00:00.000Z'),\n\t\t\t});\n\t\t\tfor (const record of [standing, revoked, lapsed]) {\n\t\t\t\tawait stores.sessions.insertSession(record);\n\t\t\t}\n\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(standing.tokenHash),\n\t\t\t\tstanding,\n\t\t\t\t'findSessionByTokenHash for a standing session',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(revoked.tokenHash),\n\t\t\t\trevoked,\n\t\t\t\t'findSessionByTokenHash for a revoked session should answer it verbatim — revocation is the core’s to read',\n\t\t\t);\n\n\t\t\t// A TTL index may already have dropped it: null is allowed, a changed\n\t\t\t// record is not.\n\t\t\tconst found = await stores.sessions.findSessionByTokenHash(\n\t\t\t\tlapsed.tokenHash,\n\t\t\t);\n\t\t\tif (found !== null) {\n\t\t\t\tequal(\n\t\t\t\t\tfound,\n\t\t\t\t\tlapsed,\n\t\t\t\t\t'findSessionByTokenHash for a lapsed session should answer it verbatim, or null',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tisNull(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash('f'.repeat(64)),\n\t\t\t\t'findSessionByTokenHash for an unknown hash',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'sessions.idempotentInsert',\n\t\tgroup,\n\t\tname: 'is idempotent under retry: inserting an existing session id writes nothing',\n\t\tasync run({ stores }) {\n\t\t\tconst record = sessionRecord();\n\t\t\tawait stores.sessions.insertSession(record);\n\t\t\tawait stores.sessions.insertSession({\n\t\t\t\t...record,\n\t\t\t\texpiresAt: new Date('2099-06-01T00:00:00.000Z'),\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(record.tokenHash),\n\t\t\t\trecord,\n\t\t\t\t'insertSession retried with the same id should write nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'sessions.extend',\n\t\tgroup,\n\t\tname: 'extends a standing session, and never brings back a revoked one',\n\t\tasync run({ stores }) {\n\t\t\tconst standing = sessionRecord();\n\t\t\tconst revoked = sessionRecord({\n\t\t\t\trevokedAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\t\t\tawait stores.sessions.insertSession(standing);\n\t\t\tawait stores.sessions.insertSession(revoked);\n\t\t\tconst later = at('2099-06-01T00:00:00.000Z');\n\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.extendSession(standing.id, later),\n\t\t\t\t{ ...standing, expiresAt: later },\n\t\t\t\t'extendSession should answer the session with its new expiresAt',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.sessions.extendSession(revoked.id, later),\n\t\t\t\t'extendSession on a revoked session — an extension racing a revocation must never bring it back',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(revoked.tokenHash),\n\t\t\t\trevoked,\n\t\t\t\t'extendSession refused on a revoked session should have written nothing',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.sessions.extendSession(mintId(), later),\n\t\t\t\t'extendSession on an unknown session',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'sessions.revoke',\n\t\tgroup,\n\t\tname: 'revokes once and keeps the first revokedAt; answers false only when there is no session',\n\t\tasync run({ stores }) {\n\t\t\tconst record = sessionRecord();\n\t\t\tawait stores.sessions.insertSession(record);\n\t\t\tconst first = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.revokeSession(record.id, first),\n\t\t\t\ttrue,\n\t\t\t\t'revokeSession on a standing session',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.revokeSession(\n\t\t\t\t\trecord.id,\n\t\t\t\t\tat('2026-03-01T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\ttrue,\n\t\t\t\t'revokeSession on a revoked session still answers true: the session exists',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.sessions.findSessionByTokenHash(record.tokenHash))\n\t\t\t\t\t?.revokedAt,\n\t\t\t\tfirst,\n\t\t\t\t'revokeSession twice should keep the first revokedAt',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.revokeSession(mintId(), first),\n\t\t\t\tfalse,\n\t\t\t\t'revokeSession on an unknown session',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'sessions.revokeUser',\n\t\tgroup,\n\t\tname: 'revokes every standing session of one user but the one excepted, and counts only what it revoked',\n\t\tasync run({ stores }) {\n\t\t\tconst userId = mintId();\n\t\t\tconst current = sessionRecord({ userId });\n\t\t\tconst other = sessionRecord({ userId });\n\t\t\tconst already = sessionRecord({\n\t\t\t\tuserId,\n\t\t\t\trevokedAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\t\t\tconst stranger = sessionRecord();\n\t\t\tfor (const record of [current, other, already, stranger]) {\n\t\t\t\tawait stores.sessions.insertSession(record);\n\t\t\t}\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.revokeUserSessions(userId, now, current.id),\n\t\t\t\t1,\n\t\t\t\t'revokeUserSessions with except: how many this call revoked',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\t(await stores.sessions.findSessionByTokenHash(current.tokenHash))\n\t\t\t\t\t?.revokedAt ?? null,\n\t\t\t\t'revokeUserSessions should spare the excepted session',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.sessions.findSessionByTokenHash(already.tokenHash))\n\t\t\t\t\t?.revokedAt,\n\t\t\t\talready.revokedAt,\n\t\t\t\t'revokeUserSessions should keep an earlier revokedAt',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(stranger.tokenHash),\n\t\t\t\tstranger,\n\t\t\t\t'revokeUserSessions should not touch another user',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.revokeUserSessions(userId, now),\n\t\t\t\t1,\n\t\t\t\t'revokeUserSessions without except',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.revokeUserSessions(mintId(), now),\n\t\t\t\t0,\n\t\t\t\t'revokeUserSessions for a user with none answers 0, not a failure',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'sessions.deleteUser',\n\t\tgroup,\n\t\tname: 'deletes every session of one user — standing, revoked or lapsed — and counts them',\n\t\tasync run({ stores }) {\n\t\t\tconst userId = mintId();\n\t\t\tconst lapsed = sessionRecord({\n\t\t\t\tuserId,\n\t\t\t\texpiresAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\t\t\tconst records = [\n\t\t\t\tsessionRecord({ userId }),\n\t\t\t\tsessionRecord({ userId, revokedAt: at('2026-01-02T00:00:00.000Z') }),\n\t\t\t\tlapsed,\n\t\t\t];\n\t\t\tconst stranger = sessionRecord();\n\t\t\tfor (const record of [...records, stranger]) {\n\t\t\t\tawait stores.sessions.insertSession(record);\n\t\t\t}\n\n\t\t\t// The lapsed one may be gone already: a store with its own expiry —\n\t\t\t// a Redis key TTL — drops it as soon as it lapses, which the port\n\t\t\t// allows. Whether it is still there is what the store answers for it.\n\t\t\tconst lapsedHeld =\n\t\t\t\t(await stores.sessions.findSessionByTokenHash(lapsed.tokenHash)) !==\n\t\t\t\tnull;\n\t\t\tconst expected = lapsedHeld ? 3 : 2;\n\t\t\tconst deleted = await stores.sessions.deleteUserSessions(userId);\n\t\t\tok(\n\t\t\t\tdeleted === expected,\n\t\t\t\t`deleteUserSessions: how many it deleted, revoked and lapsed ones included — ${String(expected)}, as the lapsed one is ${lapsedHeld ? 'still held' : 'already expired'}; got ${String(deleted)}`,\n\t\t\t);\n\t\t\tfor (const record of records) {\n\t\t\t\tisNull(\n\t\t\t\t\tawait stores.sessions.findSessionByTokenHash(record.tokenHash),\n\t\t\t\t\t'findSessionByTokenHash after deleteUserSessions',\n\t\t\t\t);\n\t\t\t}\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(stranger.tokenHash),\n\t\t\t\tstranger,\n\t\t\t\t'deleteUserSessions should not touch another user',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.deleteUserSessions(userId),\n\t\t\t\t0,\n\t\t\t\t'deleteUserSessions for a user with none answers 0, not a failure',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'sessions.deleteExpired',\n\t\tgroup,\n\t\tname: 'deletes sessions whose expiry is at or before the instant, and only those',\n\t\tneeds: 'deleteExpiredSessions',\n\t\tasync run({ stores }) {\n\t\t\tconst collect = stores.sessions.deleteExpiredSessions;\n\t\t\tok(collect !== undefined, 'deleteExpiredSessions should be present');\n\t\t\tconst lapsed = sessionRecord({\n\t\t\t\texpiresAt: at('2026-01-10T00:00:00.000Z'),\n\t\t\t});\n\t\t\tconst living = sessionRecord({\n\t\t\t\texpiresAt: at('2026-01-10T00:00:00.001Z'),\n\t\t\t});\n\t\t\tawait stores.sessions.insertSession(lapsed);\n\t\t\tawait stores.sessions.insertSession(living);\n\n\t\t\tequal(\n\t\t\t\tawait collect?.call(stores.sessions, at('2026-01-10T00:00:00.000Z')),\n\t\t\t\t1,\n\t\t\t\t'deleteExpiredSessions: how many it deleted',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(lapsed.tokenHash),\n\t\t\t\t'findSessionByTokenHash after deleteExpiredSessions',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.sessions.findSessionByTokenHash(living.tokenHash),\n\t\t\t\tliving,\n\t\t\t\t'deleteExpiredSessions should keep a session one millisecond short of lapsing',\n\t\t\t);\n\t\t},\n\t},\n];\n",
|
|
9
9
|
"import { mintId } from '../../ids/id';\nimport { equal, isNull } from '../assert';\nimport { at, tokenRecord } from '../fixtures';\nimport type { ConformanceCase } from '../types';\n\nconst group = 'tokens';\n\nexport const tokenStoreCases: readonly ConformanceCase[] = [\n\t{\n\t\tid: 'tokens.consume',\n\t\tgroup,\n\t\tname: 'answers the token as it was before the call: spentAt null means this call spent it',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord();\n\t\t\tawait stores.tokens.insertToken(record);\n\t\t\tconst first = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t'resetPassword',\n\t\t\t\t\tfirst,\n\t\t\t\t),\n\t\t\t\trecord,\n\t\t\t\t'consumeToken the first time should answer the token as it was — spentAt null',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t'resetPassword',\n\t\t\t\t\tat('2026-02-02T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\t{ ...record, spentAt: first },\n\t\t\t\t'consumeToken the second time should answer it spent at the first call’s instant, and change nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.concurrency',\n\t\tgroup,\n\t\tname: 'lets exactly one of twenty concurrent redemptions spend a token — one conditional write, never a read then a write',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord();\n\t\t\tawait stores.tokens.insertToken(record);\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tconst answers = await Promise.all(\n\t\t\t\tArray.from({ length: 20 }, () =>\n\t\t\t\t\tstores.tokens.consumeToken(record.tokenHash, 'resetPassword', now),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\tanswers.filter((answer) => answer !== null && answer.spentAt === null)\n\t\t\t\t\t.length,\n\t\t\t\t1,\n\t\t\t\t'consumeToken: of twenty concurrent calls, exactly one should see spentAt null — a reset token redeemed twice is an account takeover',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.kind',\n\t\tgroup,\n\t\tname: 'does not know, and does not spend, a token of the other kind',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord({ kind: 'verifyEmail' });\n\t\t\tawait stores.tokens.insertToken(record);\n\n\t\t\tisNull(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t'resetPassword',\n\t\t\t\t\tat('2026-02-01T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\t'consumeToken for a verifyEmail token redeemed as resetPassword',\n\t\t\t);\n\t\t\tconst own = await stores.tokens.consumeToken(\n\t\t\t\trecord.tokenHash,\n\t\t\t\t'verifyEmail',\n\t\t\t\tat('2026-02-02T00:00:00.000Z'),\n\t\t\t);\n\t\t\tequal(\n\t\t\t\town,\n\t\t\t\trecord,\n\t\t\t\t'consumeToken of the other kind should not have spent the token: redeemed as its own kind, it is still unspent',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\t'0'.repeat(64),\n\t\t\t\t\t'resetPassword',\n\t\t\t\t\tat('2026-02-01T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\t'consumeToken for an unknown hash',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.countAttempt',\n\t\tgroup,\n\t\tname: 'counts an attempt at a code, and answers the token as it is after the call',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord({\n\t\t\t\tkind: 'signInCode',\n\t\t\t\tcodeHash: 'c'.repeat(64),\n\t\t\t});\n\t\t\tawait stores.tokens.insertToken(record);\n\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.countAttempt(record.tokenHash, 'signInCode'),\n\t\t\t\t{ ...record, attempts: 1 },\n\t\t\t\t'countAttempt the first time should answer the token with attempts 1, codeHash as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.countAttempt(record.tokenHash, 'signInCode'),\n\t\t\t\t{ ...record, attempts: 2 },\n\t\t\t\t'countAttempt the second time should answer attempts 2',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t'signInCode',\n\t\t\t\t\tat('2026-02-01T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\t{ ...record, attempts: 2 },\n\t\t\t\t'consumeToken should answer the attempts counted, and spend the token',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.countAttemptConcurrency',\n\t\tgroup,\n\t\tname: 'gives twenty concurrent attempts twenty distinct counts — one conditional write, never a read then a write',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord({ kind: 'secondFactor' });\n\t\t\tawait stores.tokens.insertToken(record);\n\n\t\t\tconst answers = await Promise.all(\n\t\t\t\tArray.from({ length: 20 }, () =>\n\t\t\t\t\tstores.tokens.countAttempt(record.tokenHash, 'secondFactor'),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\tanswers\n\t\t\t\t\t.map((answer) => answer?.attempts)\n\t\t\t\t\t.sort((a, b) => (a ?? 0) - (b ?? 0)),\n\t\t\t\tArray.from({ length: 20 }, (_, index) => index + 1),\n\t\t\t\t'countAttempt: twenty concurrent calls should answer 1 to 20, each once — two guesses reading one count is a guess for free',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.countAttemptRace',\n\t\tgroup,\n\t\tname: 'never counts an attempt once the token is spent, when attempts and a redemption race',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord({ kind: 'signInCode' });\n\t\t\tawait stores.tokens.insertToken(record);\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\t\t\tconst count = () =>\n\t\t\t\tstores.tokens.countAttempt(record.tokenHash, 'signInCode');\n\n\t\t\tconst answers = await Promise.all([\n\t\t\t\t...Array.from({ length: 10 }, count),\n\t\t\t\tstores.tokens.consumeToken(record.tokenHash, 'signInCode', now),\n\t\t\t\t...Array.from({ length: 10 }, count),\n\t\t\t]);\n\t\t\tconst final = await stores.tokens.consumeToken(\n\t\t\t\trecord.tokenHash,\n\t\t\t\t'signInCode',\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tif (final === null) {\n\t\t\t\tthrow new Error('consumeToken after the race should answer the token');\n\t\t\t}\n\t\t\tconst counted = answers\n\t\t\t\t.slice(0, 10)\n\t\t\t\t.concat(answers.slice(11))\n\t\t\t\t.filter((answer) => answer !== null && answer.spentAt === null)\n\t\t\t\t.map((answer) => answer?.attempts ?? 0)\n\t\t\t\t.sort((a, b) => a - b);\n\n\t\t\tequal(\n\t\t\t\tcounted,\n\t\t\t\tArray.from({ length: final.attempts }, (_, index) => index + 1),\n\t\t\t\t'countAttempt: the attempts answered unspent should be 1 to the final count, each once',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tanswers\n\t\t\t\t\t.filter((answer) => answer !== null && answer.spentAt !== null)\n\t\t\t\t\t.every((answer) => answer?.attempts === final.attempts),\n\t\t\t\ttrue,\n\t\t\t\t'countAttempt: every answer after the spend should carry the final count — an attempt counted on a spent token is one the limit never saw',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.countAttemptSpent',\n\t\tgroup,\n\t\tname: 'does not count an attempt at a spent token, nor at a token of another kind, nor at none',\n\t\tasync run({ stores }) {\n\t\t\tconst spent = tokenRecord({\n\t\t\t\tkind: 'signInCode',\n\t\t\t\tattempts: 3,\n\t\t\t\tspentAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\t\t\tconst other = tokenRecord({ kind: 'resetPassword' });\n\t\t\tawait stores.tokens.insertToken(spent);\n\t\t\tawait stores.tokens.insertToken(other);\n\n\t\t\tfor (const call of ['first', 'second']) {\n\t\t\t\tequal(\n\t\t\t\t\tawait stores.tokens.countAttempt(spent.tokenHash, 'signInCode'),\n\t\t\t\t\tspent,\n\t\t\t\t\t`countAttempt on a spent token, the ${call} time, should answer it as it is: attempts still 3, nothing written`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tisNull(\n\t\t\t\tawait stores.tokens.countAttempt(other.tokenHash, 'signInCode'),\n\t\t\t\t'countAttempt for a resetPassword token counted as signInCode',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\tother.tokenHash,\n\t\t\t\t\t'resetPassword',\n\t\t\t\t\tat('2026-02-01T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\tother,\n\t\t\t\t'countAttempt of the other kind should not have counted: attempts still 0',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.tokens.countAttempt('0'.repeat(64), 'signInCode'),\n\t\t\t\t'countAttempt for an unknown hash',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.spendUserTokens',\n\t\tgroup,\n\t\tname: 'spends every unspent token of one user and one kind, and counts them, leaving the rest as they were',\n\t\tasync run({ stores }) {\n\t\t\tconst userId = mintId();\n\t\t\tconst first = tokenRecord({ userId, kind: 'signInCode' });\n\t\t\tconst second = tokenRecord({ userId, kind: 'signInCode', attempts: 2 });\n\t\t\tconst spentBefore = at('2026-01-02T00:00:00.000Z');\n\t\t\tconst spent = tokenRecord({\n\t\t\t\tuserId,\n\t\t\t\tkind: 'signInCode',\n\t\t\t\tspentAt: spentBefore,\n\t\t\t});\n\t\t\tconst otherKind = tokenRecord({ userId, kind: 'resetPassword' });\n\t\t\tconst otherUser = tokenRecord({ kind: 'signInCode' });\n\t\t\tfor (const record of [first, second, spent, otherKind, otherUser]) {\n\t\t\t\tawait stores.tokens.insertToken(record);\n\t\t\t}\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.spendUserTokens(userId, 'signInCode', now),\n\t\t\t\t2,\n\t\t\t\t'spendUserTokens: how many it spent — the two unspent signInCode tokens, not the one already spent',\n\t\t\t);\n\t\t\tfor (const record of [first, second]) {\n\t\t\t\tequal(\n\t\t\t\t\tawait stores.tokens.consumeToken(record.tokenHash, 'signInCode', now),\n\t\t\t\t\t{ ...record, spentAt: now },\n\t\t\t\t\t'spendUserTokens should have spent it at `at`, keeping its attempts',\n\t\t\t\t);\n\t\t\t}\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(spent.tokenHash, 'signInCode', now),\n\t\t\t\tspent,\n\t\t\t\t'spendUserTokens should keep the spentAt of a token already spent',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\totherKind.tokenHash,\n\t\t\t\t\t'resetPassword',\n\t\t\t\t\tnow,\n\t\t\t\t),\n\t\t\t\totherKind,\n\t\t\t\t'spendUserTokens should not touch a token of another kind',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\totherUser.tokenHash,\n\t\t\t\t\t'signInCode',\n\t\t\t\t\tnow,\n\t\t\t\t),\n\t\t\t\totherUser,\n\t\t\t\t'spendUserTokens should not touch another user',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.spendUserTokens(userId, 'signInCode', now),\n\t\t\t\t0,\n\t\t\t\t'spendUserTokens for a user with none left unspent answers 0, not a failure',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.spendUserTokens(mintId(), 'signInCode', now),\n\t\t\t\t0,\n\t\t\t\t'spendUserTokens for a user with no token at all answers 0',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.spendUserTokensExcept',\n\t\tgroup,\n\t\tname: 'spares the token named by except, and spends the rest',\n\t\tasync run({ stores }) {\n\t\t\tconst userId = mintId();\n\t\t\tconst kept = tokenRecord({ userId, kind: 'signInCode' });\n\t\t\tconst others = [\n\t\t\t\ttokenRecord({ userId, kind: 'signInCode' }),\n\t\t\t\ttokenRecord({ userId, kind: 'signInCode' }),\n\t\t\t];\n\t\t\tfor (const record of [kept, ...others]) {\n\t\t\t\tawait stores.tokens.insertToken(record);\n\t\t\t}\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.spendUserTokens(\n\t\t\t\t\tuserId,\n\t\t\t\t\t'signInCode',\n\t\t\t\t\tnow,\n\t\t\t\t\tkept.tokenHash,\n\t\t\t\t),\n\t\t\t\t2,\n\t\t\t\t'spendUserTokens with except: how many it spent — every token but the one named',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(kept.tokenHash, 'signInCode', now),\n\t\t\t\tkept,\n\t\t\t\t'spendUserTokens should not spend the token named by except',\n\t\t\t);\n\t\t\tfor (const record of others) {\n\t\t\t\tequal(\n\t\t\t\t\t(\n\t\t\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t\t\t'signInCode',\n\t\t\t\t\t\t\tnow,\n\t\t\t\t\t\t)\n\t\t\t\t\t)?.spentAt,\n\t\t\t\t\tnow,\n\t\t\t\t\t'spendUserTokens with except should spend every other token',\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.spendUserTokensRace',\n\t\tgroup,\n\t\tname: \"spends a token once, when spending a user's tokens races a redemption\",\n\t\tasync run({ stores }) {\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\t\t\tfor (let round = 0; round < 10; round += 1) {\n\t\t\t\tconst record = tokenRecord({ kind: 'signInCode' });\n\t\t\t\tawait stores.tokens.insertToken(record);\n\n\t\t\t\tconst [counted, redeemed] = await Promise.all([\n\t\t\t\t\tstores.tokens.spendUserTokens(record.userId, 'signInCode', now),\n\t\t\t\t\tstores.tokens.consumeToken(record.tokenHash, 'signInCode', now),\n\t\t\t\t]);\n\n\t\t\t\tequal(\n\t\t\t\t\tcounted + (redeemed?.spentAt === null ? 1 : 0),\n\t\t\t\t\t1,\n\t\t\t\t\t'spendUserTokens and consumeToken: exactly one of the two should have spent the token',\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.challenge',\n\t\tgroup,\n\t\tname: \"keeps a second-factor challenge, whose address is '' — nothing was sent for it\",\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord({ kind: 'secondFactor', address: '' });\n\t\t\tawait stores.tokens.insertToken(record);\n\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.countAttempt(record.tokenHash, 'secondFactor'),\n\t\t\t\t{ ...record, attempts: 1 },\n\t\t\t\t\"countAttempt should answer a challenge with its empty address kept — a store that refuses '' refuses every sign-in with a second factor\",\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t'secondFactor',\n\t\t\t\t\tat('2026-02-01T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\t{ ...record, attempts: 1 },\n\t\t\t\t'consumeToken should spend the challenge and answer it as it was',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.lapsed',\n\t\tgroup,\n\t\tname: 'spends a lapsed token all the same: expiry is compared by the core, after the call',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord({ expiresAt: at('2020-01-01T00:00:00.000Z') });\n\t\t\tawait stores.tokens.insertToken(record);\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tconst first = await stores.tokens.consumeToken(\n\t\t\t\trecord.tokenHash,\n\t\t\t\t'resetPassword',\n\t\t\t\tnow,\n\t\t\t);\n\t\t\t// A store may have dropped a lapsed token already; if it answers, it\n\t\t\t// answers it unspent, and spends it.\n\t\t\tif (first !== null) {\n\t\t\t\tequal(\n\t\t\t\t\tfirst,\n\t\t\t\t\trecord,\n\t\t\t\t\t'consumeToken on a lapsed token should answer it as it was',\n\t\t\t\t);\n\t\t\t\tequal(\n\t\t\t\t\t(\n\t\t\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t\t\t'resetPassword',\n\t\t\t\t\t\t\tnow,\n\t\t\t\t\t\t)\n\t\t\t\t\t)?.spentAt,\n\t\t\t\t\tnow,\n\t\t\t\t\t'consumeToken should have spent the lapsed token, so it can never be retried',\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.idempotentInsert',\n\t\tgroup,\n\t\tname: 'is idempotent under retry: inserting an existing token hash writes nothing',\n\t\tasync run({ stores }) {\n\t\t\tconst record = tokenRecord();\n\t\t\tawait stores.tokens.insertToken(record);\n\t\t\tawait stores.tokens.insertToken({\n\t\t\t\t...record,\n\t\t\t\taddress: 'other@example.test',\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\trecord.tokenHash,\n\t\t\t\t\t'resetPassword',\n\t\t\t\t\tat('2026-02-01T00:00:00.000Z'),\n\t\t\t\t),\n\t\t\t\trecord,\n\t\t\t\t'insertToken retried with the same hash should write nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'tokens.deleteUser',\n\t\tgroup,\n\t\tname: 'deletes every token of one user, spent or not, of either kind, and counts them',\n\t\tasync run({ stores }) {\n\t\t\tconst userId = mintId();\n\t\t\tconst records = [\n\t\t\t\ttokenRecord({ userId }),\n\t\t\t\ttokenRecord({ userId, kind: 'verifyEmail' }),\n\t\t\t\ttokenRecord({ userId, spentAt: at('2026-01-02T00:00:00.000Z') }),\n\t\t\t];\n\t\t\tconst stranger = tokenRecord();\n\t\t\tfor (const record of [...records, stranger]) {\n\t\t\t\tawait stores.tokens.insertToken(record);\n\t\t\t}\n\t\t\tconst now = at('2026-02-01T00:00:00.000Z');\n\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.deleteUserTokens(userId),\n\t\t\t\t3,\n\t\t\t\t'deleteUserTokens: how many it deleted',\n\t\t\t);\n\t\t\tfor (const record of records) {\n\t\t\t\tisNull(\n\t\t\t\t\tawait stores.tokens.consumeToken(record.tokenHash, record.kind, now),\n\t\t\t\t\t'consumeToken after deleteUserTokens',\n\t\t\t\t);\n\t\t\t}\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.consumeToken(\n\t\t\t\t\tstranger.tokenHash,\n\t\t\t\t\tstranger.kind,\n\t\t\t\t\tnow,\n\t\t\t\t),\n\t\t\t\tstranger,\n\t\t\t\t'deleteUserTokens should not touch another user',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.tokens.deleteUserTokens(userId),\n\t\t\t\t0,\n\t\t\t\t'deleteUserTokens for a user with none answers 0, not a failure',\n\t\t\t);\n\t\t},\n\t},\n];\n",
|
|
10
|
-
"import type { UserRecord } from '../../auth/port/types';\nimport { NotFoundError, StoreConflict } from '../../errors/janus-error';\nimport { mintId } from '../../ids/id';\nimport { equal, isNull, isOurs, ok, rejects } from '../assert';\nimport { at, userRecord } from '../fixtures';\nimport type { ConformanceCase } from '../types';\n\nconst group = 'users';\n\n/** A second factor as the core writes it: the secret sealed, opaque to a store. */\nconst secondFactor = {\n\tmethod: 'totp',\n\tsecret: 'v1.2026-09.aXY.Y2lwaGVydGV4dA',\n\tconfirmedAt: null,\n\tlastStep: null,\n} as const;\n\nexport const userStoreCases: readonly ConformanceCase[] = [\n\t{\n\t\tid: 'users.roundTrip',\n\t\tgroup,\n\t\tname: 'round-trips a record byte for byte: nested fields, a 1 that stays a number, a \"1\" that stays a string',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord({\n\t\t\t\tfields: {\n\t\t\t\t\temail: 'Ada@Example.test',\n\t\t\t\t\tname: { first: 'Ada', last: 'Lovelace' },\n\t\t\t\t\tcount: 1,\n\t\t\t\t\tcode: '1',\n\t\t\t\t\tzero: 0,\n\t\t\t\t\tflags: [true, false, null],\n\t\t\t\t\tnote: ' spaced ',\n\t\t\t\t\tunicode: 'Bob — ÿ',\n\t\t\t\t\tnested: { deep: { deeper: [1, { two: 2 }] } },\n\t\t\t\t\ttags: ['a', 'b'],\n\t\t\t\t},\n\t\t\t\temailVerifiedAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tawait stores.users.insertUser(record),\n\t\t\t\trecord,\n\t\t\t\t'insertUser should answer the record as stored',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\trecord,\n\t\t\t\t'findUser should answer the record exactly as it was written',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.edgeCharacters',\n\t\tgroup,\n\t\tname: 'round-trips every character the core lets through: control characters, U+FFFF and a surrogate pair',\n\t\tasync run({ stores }) {\n\t\t\tconst edge = 'a\\u0001\\u001f\\u007f\\uFFFF 😀 z';\n\t\t\tconst record = userRecord({\n\t\t\t\tlogins: [edge],\n\t\t\t\tfields: { email: 'ada@example.test', [edge]: [edge, { [edge]: edge }] },\n\t\t\t});\n\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\trecord,\n\t\t\t\t'findUser should answer every edge character exactly as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', edge))?.id,\n\t\t\t\trecord.id,\n\t\t\t\t'findUserByLogin should find a login made of edge characters',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.absence',\n\t\tgroup,\n\t\tname: 'answers null for an absence, never undefined, and an empty page for an empty store',\n\t\tasync run({ stores }) {\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUser(mintId()),\n\t\t\t\t'findUser for an unknown id',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUserByLogin('user', 'nobody@example.test'),\n\t\t\t\t'findUserByLogin for an unknown login',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.listUsers({ type: 'user', after: null, limit: 10 }),\n\t\t\t\t{ items: [], nextCursor: null },\n\t\t\t\t'listUsers on an empty store should answer an empty page',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.loginBytes',\n\t\tgroup,\n\t\tname: 'compares logins as bytes, within one type: no case folding, no trimming, no collation',\n\t\tasync run({ stores }) {\n\t\t\tconst upper = userRecord({ logins: ['Ada@Example.test'] });\n\t\t\tconst lower = userRecord({ logins: ['ada@example.test'] });\n\t\t\tconst staff = userRecord({ type: 'staff', logins: ['ada@example.test'] });\n\n\t\t\tfor (const record of [upper, lower, staff]) {\n\t\t\t\tawait stores.users.insertUser(record);\n\t\t\t}\n\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', 'Ada@Example.test'))?.id,\n\t\t\t\tupper.id,\n\t\t\t\t'findUserByLogin should find the login with its exact bytes',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', 'ada@example.test'))?.id,\n\t\t\t\tlower.id,\n\t\t\t\t'findUserByLogin should tell \"Ada@…\" from \"ada@…\": normalisation is the core’s',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('staff', 'ada@example.test'))?.id,\n\t\t\t\tstaff.id,\n\t\t\t\t'uniqueness is of (type, login): the same login under another type is another user',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.idempotentInsert',\n\t\tgroup,\n\t\tname: 'is idempotent under retry: inserting an existing id answers the stored record and writes nothing',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst retried = await stores.users.insertUser({\n\t\t\t\t...record,\n\t\t\t\tfields: { email: 'changed@example.test' },\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tretried,\n\t\t\t\trecord,\n\t\t\t\t'insertUser retried with the same id should answer the stored record',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\trecord,\n\t\t\t\t'insertUser retried should write nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.uniqueness',\n\t\tgroup,\n\t\tname: 'lets exactly one of twenty concurrent inserts hold a login: uniqueness is a constraint, not a read',\n\t\tasync run({ stores }) {\n\t\t\tconst records = Array.from({ length: 20 }, () =>\n\t\t\t\tuserRecord({ logins: ['contested@example.test'] }),\n\t\t\t);\n\n\t\t\tconst outcomes = await Promise.allSettled(\n\t\t\t\trecords.map((record) => stores.users.insertUser(record)),\n\t\t\t);\n\t\t\tconst accepted = outcomes.filter((o) => o.status === 'fulfilled');\n\t\t\tconst refused = outcomes.flatMap((o) =>\n\t\t\t\to.status === 'rejected' ? [o.reason as unknown] : [],\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\taccepted.length,\n\t\t\t\t1,\n\t\t\t\t'insertUser: of twenty concurrent inserts of one login, exactly one should be accepted — a read followed by a write lets several through',\n\t\t\t);\n\t\t\tfor (const error of refused) {\n\t\t\t\tisOurs(\n\t\t\t\t\terror,\n\t\t\t\t\tStoreConflict,\n\t\t\t\t\t'insertUser should refuse a taken login with StoreConflict',\n\t\t\t\t);\n\t\t\t\tequal(error.code, 'LOGIN_TAKEN', 'insertUser: the conflict code');\n\t\t\t\tequal(\n\t\t\t\t\terror.login,\n\t\t\t\t\t'contested@example.test',\n\t\t\t\t\t'insertUser: the conflict should name the login',\n\t\t\t\t);\n\t\t\t\tok(\n\t\t\t\t\t!error.message.includes('contested@example.test'),\n\t\t\t\t\t`insertUser: the conflict's message should not quote the login — a message never carries a value; got ${JSON.stringify(error.message)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tlet stored = 0;\n\t\t\tfor (const record of records) {\n\t\t\t\tif ((await stores.users.findUser(record.id)) !== null) stored += 1;\n\t\t\t}\n\t\t\tequal(stored, 1, 'insertUser: a refused insert should write nothing');\n\t\t},\n\t},\n\t{\n\t\tid: 'users.versionIncrements',\n\t\tgroup,\n\t\tname: 'applies an update at the expected version, and answers the record written with its version one higher',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst written = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), active: false },\n\t\t\t\t0,\n\t\t\t);\n\t\t\tconst expected: UserRecord = {\n\t\t\t\t...record,\n\t\t\t\tactive: false,\n\t\t\t\tversion: 1,\n\t\t\t\tupdatedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t};\n\n\t\t\tequal(\n\t\t\t\twritten,\n\t\t\t\texpected,\n\t\t\t\t'updateUser should answer the record as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\texpected,\n\t\t\t\t'findUser after updateUser',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.versionConflict',\n\t\tgroup,\n\t\tname: 'refuses a stale version with both versions, and leaves the record identical byte for byte',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tawait stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), active: false },\n\t\t\t\t0,\n\t\t\t);\n\t\t\tconst before = await stores.users.findUser(record.id);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tstores.users.updateUser(\n\t\t\t\t\trecord.id,\n\t\t\t\t\t{\n\t\t\t\t\t\tupdatedAt: at('2026-03-01T00:00:00.000Z'),\n\t\t\t\t\t\tactive: true,\n\t\t\t\t\t\tfields: { email: 'overwritten@example.test' },\n\t\t\t\t\t},\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\t'updateUser at a stale version should reject',\n\t\t\t);\n\n\t\t\tisOurs(\n\t\t\t\terror,\n\t\t\t\tStoreConflict,\n\t\t\t\t'updateUser at a stale version should reject with StoreConflict',\n\t\t\t);\n\t\t\tequal(error.code, 'VERSION_CONFLICT', 'updateUser: the conflict code');\n\t\t\tequal(error.expectedVersion, 0, 'updateUser: expectedVersion');\n\t\t\tequal(error.actualVersion, 1, 'updateUser: actualVersion');\n\t\t\t// A partial write that also reports a conflict is the worst of both.\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\tbefore,\n\t\t\t\t'updateUser refused for its version should have written nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.unknownUpdate',\n\t\tgroup,\n\t\tname: 'refuses an update to an unknown id with NotFoundError, told apart from a version conflict',\n\t\tasync run({ stores }) {\n\t\t\tconst error = await rejects(\n\t\t\t\tstores.users.updateUser(\n\t\t\t\t\tmintId(),\n\t\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z') },\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\t'updateUser on an unknown id should reject',\n\t\t\t);\n\n\t\t\tisOurs(\n\t\t\t\terror,\n\t\t\t\tNotFoundError,\n\t\t\t\t'updateUser on an unknown id should reject with NotFoundError, not a version conflict',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.omission',\n\t\tgroup,\n\t\tname: 'omission — the Kratos PUT trap: an update leaves every field it does not name exactly as it was',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord({\n\t\t\t\tfields: { email: 'kept@example.test', plan: 'pro' },\n\t\t\t\tsecondFactor: {\n\t\t\t\t\t...secondFactor,\n\t\t\t\t\tconfirmedAt: at('2026-01-03T00:00:00.000Z'),\n\t\t\t\t\tlastStep: 59_000_000,\n\t\t\t\t},\n\t\t\t\temailVerifiedAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst written = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), schemaVersion: '2' },\n\t\t\t\t0,\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\twritten,\n\t\t\t\t{\n\t\t\t\t\t...record,\n\t\t\t\t\tschemaVersion: '2',\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tupdatedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t\t},\n\t\t\t\t'updateUser naming only schemaVersion should leave active, fields, logins, password, secondFactor and emailVerifiedAt untouched',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.passwordSlot',\n\t\tgroup,\n\t\tname: 'keeps a password the patch does not name, and removes one the patch names null',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst kept = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z') },\n\t\t\t\t0,\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tkept.password,\n\t\t\t\trecord.password,\n\t\t\t\t'updateUser not naming password should keep it',\n\t\t\t);\n\n\t\t\tconst removed = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-02T00:00:00.000Z'), password: null },\n\t\t\t\t1,\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tremoved.password,\n\t\t\t\t'updateUser with password: null should remove the password',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.secondFactorSlot',\n\t\tgroup,\n\t\tname: 'round-trips a second factor, keeps it when a patch does not name it, and removes it when the patch names null',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord({ secondFactor });\n\t\t\tequal(\n\t\t\t\tawait stores.users.insertUser(record),\n\t\t\t\trecord,\n\t\t\t\t'insertUser should answer a pending second factor as written: confirmedAt and lastStep null',\n\t\t\t);\n\n\t\t\tconst confirmed = {\n\t\t\t\t...secondFactor,\n\t\t\t\tconfirmedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t\tlastStep: 59_000_000,\n\t\t\t};\n\t\t\tequal(\n\t\t\t\t(\n\t\t\t\t\tawait stores.users.updateUser(\n\t\t\t\t\t\trecord.id,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tupdatedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t\t\t\t\tsecondFactor: confirmed,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t0,\n\t\t\t\t\t)\n\t\t\t\t).secondFactor,\n\t\t\t\tconfirmed,\n\t\t\t\t'updateUser naming secondFactor should replace it whole',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(\n\t\t\t\t\tawait stores.users.updateUser(\n\t\t\t\t\t\trecord.id,\n\t\t\t\t\t\t{ updatedAt: at('2026-02-02T00:00:00.000Z') },\n\t\t\t\t\t\t1,\n\t\t\t\t\t)\n\t\t\t\t).secondFactor,\n\t\t\t\tconfirmed,\n\t\t\t\t'updateUser not naming secondFactor should keep it',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUser(record.id))?.secondFactor,\n\t\t\t\tconfirmed,\n\t\t\t\t'findUser should answer the second factor as written',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\t(\n\t\t\t\t\tawait stores.users.updateUser(\n\t\t\t\t\t\trecord.id,\n\t\t\t\t\t\t{ updatedAt: at('2026-02-03T00:00:00.000Z'), secondFactor: null },\n\t\t\t\t\t\t2,\n\t\t\t\t\t)\n\t\t\t\t).secondFactor,\n\t\t\t\t'updateUser with secondFactor: null should remove the second factor',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.loginsMove',\n\t\tgroup,\n\t\tname: 'moves logins on update: the old one is free, the new one found, one held elsewhere refused',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tconst other = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tawait stores.users.insertUser(other);\n\t\t\tconst [old] = record.logins;\n\t\t\tconst moved = 'moved@example.test';\n\n\t\t\tawait stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), logins: [moved] },\n\t\t\t\t0,\n\t\t\t);\n\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUserByLogin('user', old ?? ''),\n\t\t\t\t'findUserByLogin for a login the update replaced',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', moved))?.id,\n\t\t\t\trecord.id,\n\t\t\t\t'findUserByLogin for the login the update wrote',\n\t\t\t);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tstores.users.updateUser(\n\t\t\t\t\tother.id,\n\t\t\t\t\t{ updatedAt: at('2026-02-02T00:00:00.000Z'), logins: [moved] },\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\t'updateUser onto a login another user holds should reject',\n\t\t\t);\n\t\t\tisOurs(\n\t\t\t\terror,\n\t\t\t\tStoreConflict,\n\t\t\t\t'updateUser onto a held login should reject with StoreConflict',\n\t\t\t);\n\t\t\tequal(error.code, 'LOGIN_TAKEN', 'updateUser: the conflict code');\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(other.id),\n\t\t\t\tother,\n\t\t\t\t'updateUser refused for a login should have written nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.pagination',\n\t\tgroup,\n\t\tname: 'pages 25 users of one type by 10 in ascending id order, with no gap, no repeat and no other type, and ends on a null cursor',\n\t\tasync run({ stores }) {\n\t\t\tconst records = Array.from({ length: 25 }, () => userRecord());\n\t\t\tconst others = Array.from({ length: 5 }, () =>\n\t\t\t\tuserRecord({ type: 'staff' }),\n\t\t\t);\n\t\t\t// Written newest first, and interleaved with another type, so neither\n\t\t\t// insertion order nor the whole collection is the answer.\n\t\t\tfor (const [index, record] of [...records].reverse().entries()) {\n\t\t\t\tawait stores.users.insertUser(record);\n\t\t\t\tconst other = others[index];\n\t\t\t\tif (other !== undefined) await stores.users.insertUser(other);\n\t\t\t}\n\t\t\tconst expected = records.map((record) => record.id).sort();\n\n\t\t\tconst seen: string[] = [];\n\t\t\tconst sizes: number[] = [];\n\t\t\tlet after: string | null = null;\n\t\t\tfor (let page = 0; page < 10; page += 1) {\n\t\t\t\tconst answer = await stores.users.listUsers({\n\t\t\t\t\ttype: 'user',\n\t\t\t\t\tafter,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t});\n\t\t\t\tseen.push(...answer.items.map((item) => item.id));\n\t\t\t\tsizes.push(answer.items.length);\n\t\t\t\tafter = answer.nextCursor;\n\t\t\t\tif (after === null) break;\n\t\t\t}\n\n\t\t\tequal(sizes, [10, 10, 5], 'listUsers: page sizes for 25 users by 10');\n\t\t\tequal(\n\t\t\t\tseen,\n\t\t\t\texpected,\n\t\t\t\t'listUsers: every id of the type once, in ascending order',\n\t\t\t);\n\n\t\t\t// The cursor need not name a stored user.\n\t\t\tconst between = await stores.users.listUsers({\n\t\t\t\ttype: 'user',\n\t\t\t\tafter: '00000000-0000-7000-8000-000000000000',\n\t\t\t\tlimit: 3,\n\t\t\t});\n\t\t\tok(\n\t\t\t\tbetween.items[0]?.id === expected[0],\n\t\t\t\t'listUsers: a cursor naming no stored user should start after it, not fail',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.delete',\n\t\tgroup,\n\t\tname: 'deletes a user and frees their logins, answers false for nobody, and touches no other user',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tconst other = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tawait stores.users.insertUser(other);\n\t\t\tconst [login] = record.logins;\n\n\t\t\tequal(\n\t\t\t\tawait stores.users.deleteUser(record.id),\n\t\t\t\ttrue,\n\t\t\t\t'deleteUser for a stored user should answer true',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\t'findUser after deleteUser',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUserByLogin(record.type, login ?? ''),\n\t\t\t\t'findUserByLogin for a deleted user’s login',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.deleteUser(record.id),\n\t\t\t\tfalse,\n\t\t\t\t'deleteUser replayed should answer false, not fail: a deletion interrupted half-way is replayed',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(other.id),\n\t\t\t\tother,\n\t\t\t\t'deleteUser should not touch another user',\n\t\t\t);\n\t\t\t// The login is free for a new account: the index forgot it.\n\t\t\tconst successor = userRecord({ logins: [login ?? ''] });\n\t\t\tequal(\n\t\t\t\tawait stores.users.insertUser(successor),\n\t\t\t\tsuccessor,\n\t\t\t\t'insertUser onto a deleted user’s login should succeed',\n\t\t\t);\n\t\t},\n\t},\n];\n",
|
|
10
|
+
"import type { UserRecord } from '../../auth/port/types';\nimport { NotFoundError, StoreConflict } from '../../errors/janus-error';\nimport { mintId } from '../../ids/id';\nimport { equal, isNull, isOurs, ok, rejects } from '../assert';\nimport { at, userRecord } from '../fixtures';\nimport type { ConformanceCase } from '../types';\n\nconst group = 'users';\n\n/** A second factor as the core writes it: the secret sealed, opaque to a store. */\nconst secondFactor = {\n\tmethod: 'totp',\n\tsecret: 'v1.2026-09.aXY.Y2lwaGVydGV4dA',\n\tconfirmedAt: null,\n\tlastStep: null,\n} as const;\n\nexport const userStoreCases: readonly ConformanceCase[] = [\n\t{\n\t\tid: 'users.roundTrip',\n\t\tgroup,\n\t\tname: 'round-trips a record byte for byte: nested fields, a 1 that stays a number, a \"1\" that stays a string',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord({\n\t\t\t\tfields: {\n\t\t\t\t\temail: 'Ada@Example.test',\n\t\t\t\t\tname: { first: 'Ada', last: 'Lovelace' },\n\t\t\t\t\tcount: 1,\n\t\t\t\t\tcode: '1',\n\t\t\t\t\tzero: 0,\n\t\t\t\t\tflags: [true, false, null],\n\t\t\t\t\tnote: ' spaced ',\n\t\t\t\t\tunicode: 'Bob — ÿ',\n\t\t\t\t\tnested: { deep: { deeper: [1, { two: 2 }] } },\n\t\t\t\t\ttags: ['a', 'b'],\n\t\t\t\t},\n\t\t\t\temailVerifiedAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tawait stores.users.insertUser(record),\n\t\t\t\trecord,\n\t\t\t\t'insertUser should answer the record as stored',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\trecord,\n\t\t\t\t'findUser should answer the record exactly as it was written',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.edgeCharacters',\n\t\tgroup,\n\t\tname: 'round-trips every character the core lets through: control characters, U+FFFF and a surrogate pair',\n\t\tasync run({ stores }) {\n\t\t\tconst edge = 'a\\u0001\\u001f\\u007f\\uFFFF 😀 z';\n\t\t\tconst record = userRecord({\n\t\t\t\tlogins: [edge],\n\t\t\t\tfields: { email: 'ada@example.test', [edge]: [edge, { [edge]: edge }] },\n\t\t\t});\n\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\trecord,\n\t\t\t\t'findUser should answer every edge character exactly as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', edge))?.id,\n\t\t\t\trecord.id,\n\t\t\t\t'findUserByLogin should find a login made of edge characters',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.absence',\n\t\tgroup,\n\t\tname: 'answers null for an absence, never undefined, and an empty page for an empty store',\n\t\tasync run({ stores }) {\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUser(mintId()),\n\t\t\t\t'findUser for an unknown id',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUserByLogin('user', 'nobody@example.test'),\n\t\t\t\t'findUserByLogin for an unknown login',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.listUsers({ type: 'user', after: null, limit: 10 }),\n\t\t\t\t{ items: [], nextCursor: null },\n\t\t\t\t'listUsers on an empty store should answer an empty page',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.loginBytes',\n\t\tgroup,\n\t\tname: 'compares logins as bytes, within one type: no case folding, no trimming, no collation',\n\t\tasync run({ stores }) {\n\t\t\tconst upper = userRecord({ logins: ['Ada@Example.test'] });\n\t\t\tconst lower = userRecord({ logins: ['ada@example.test'] });\n\t\t\tconst staff = userRecord({ type: 'staff', logins: ['ada@example.test'] });\n\n\t\t\tfor (const record of [upper, lower, staff]) {\n\t\t\t\tawait stores.users.insertUser(record);\n\t\t\t}\n\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', 'Ada@Example.test'))?.id,\n\t\t\t\tupper.id,\n\t\t\t\t'findUserByLogin should find the login with its exact bytes',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', 'ada@example.test'))?.id,\n\t\t\t\tlower.id,\n\t\t\t\t'findUserByLogin should tell \"Ada@…\" from \"ada@…\": normalisation is the core’s',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('staff', 'ada@example.test'))?.id,\n\t\t\t\tstaff.id,\n\t\t\t\t'uniqueness is of (type, login): the same login under another type is another user',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.idempotentInsert',\n\t\tgroup,\n\t\tname: 'is idempotent under retry: inserting an existing id answers the stored record and writes nothing',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst retried = await stores.users.insertUser({\n\t\t\t\t...record,\n\t\t\t\tfields: { email: 'changed@example.test' },\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tretried,\n\t\t\t\trecord,\n\t\t\t\t'insertUser retried with the same id should answer the stored record',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\trecord,\n\t\t\t\t'insertUser retried should write nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.uniqueness',\n\t\tgroup,\n\t\tname: 'lets exactly one of twenty concurrent inserts hold a login: uniqueness is a constraint, not a read',\n\t\tasync run({ stores }) {\n\t\t\tconst records = Array.from({ length: 20 }, () =>\n\t\t\t\tuserRecord({ logins: ['contested@example.test'] }),\n\t\t\t);\n\n\t\t\tconst outcomes = await Promise.allSettled(\n\t\t\t\trecords.map((record) => stores.users.insertUser(record)),\n\t\t\t);\n\t\t\tconst accepted = outcomes.filter((o) => o.status === 'fulfilled');\n\t\t\tconst refused = outcomes.flatMap((o) =>\n\t\t\t\to.status === 'rejected' ? [o.reason as unknown] : [],\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\taccepted.length,\n\t\t\t\t1,\n\t\t\t\t'insertUser: of twenty concurrent inserts of one login, exactly one should be accepted — a read followed by a write lets several through',\n\t\t\t);\n\t\t\tfor (const error of refused) {\n\t\t\t\tisOurs(\n\t\t\t\t\terror,\n\t\t\t\t\tStoreConflict,\n\t\t\t\t\t'StoreConflict',\n\t\t\t\t\t'insertUser should refuse a taken login with StoreConflict',\n\t\t\t\t);\n\t\t\t\tequal(error.code, 'LOGIN_TAKEN', 'insertUser: the conflict code');\n\t\t\t\tequal(\n\t\t\t\t\terror.login,\n\t\t\t\t\t'contested@example.test',\n\t\t\t\t\t'insertUser: the conflict should name the login',\n\t\t\t\t);\n\t\t\t\tok(\n\t\t\t\t\t!error.message.includes('contested@example.test'),\n\t\t\t\t\t`insertUser: the conflict's message should not quote the login — a message never carries a value; got ${JSON.stringify(error.message)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tlet stored = 0;\n\t\t\tfor (const record of records) {\n\t\t\t\tif ((await stores.users.findUser(record.id)) !== null) stored += 1;\n\t\t\t}\n\t\t\tequal(stored, 1, 'insertUser: a refused insert should write nothing');\n\t\t},\n\t},\n\t{\n\t\tid: 'users.versionIncrements',\n\t\tgroup,\n\t\tname: 'applies an update at the expected version, and answers the record written with its version one higher',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst written = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), active: false },\n\t\t\t\t0,\n\t\t\t);\n\t\t\tconst expected: UserRecord = {\n\t\t\t\t...record,\n\t\t\t\tactive: false,\n\t\t\t\tversion: 1,\n\t\t\t\tupdatedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t};\n\n\t\t\tequal(\n\t\t\t\twritten,\n\t\t\t\texpected,\n\t\t\t\t'updateUser should answer the record as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\texpected,\n\t\t\t\t'findUser after updateUser',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.versionConflict',\n\t\tgroup,\n\t\tname: 'refuses a stale version with both versions, and leaves the record identical byte for byte',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tawait stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), active: false },\n\t\t\t\t0,\n\t\t\t);\n\t\t\tconst before = await stores.users.findUser(record.id);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tstores.users.updateUser(\n\t\t\t\t\trecord.id,\n\t\t\t\t\t{\n\t\t\t\t\t\tupdatedAt: at('2026-03-01T00:00:00.000Z'),\n\t\t\t\t\t\tactive: true,\n\t\t\t\t\t\tfields: { email: 'overwritten@example.test' },\n\t\t\t\t\t},\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\t'updateUser at a stale version should reject',\n\t\t\t);\n\n\t\t\tisOurs(\n\t\t\t\terror,\n\t\t\t\tStoreConflict,\n\t\t\t\t'StoreConflict',\n\t\t\t\t'updateUser at a stale version should reject with StoreConflict',\n\t\t\t);\n\t\t\tequal(error.code, 'VERSION_CONFLICT', 'updateUser: the conflict code');\n\t\t\tequal(error.expectedVersion, 0, 'updateUser: expectedVersion');\n\t\t\tequal(error.actualVersion, 1, 'updateUser: actualVersion');\n\t\t\t// A partial write that also reports a conflict is the worst of both.\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\tbefore,\n\t\t\t\t'updateUser refused for its version should have written nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.unknownUpdate',\n\t\tgroup,\n\t\tname: 'refuses an update to an unknown id with NotFoundError, told apart from a version conflict',\n\t\tasync run({ stores }) {\n\t\t\tconst error = await rejects(\n\t\t\t\tstores.users.updateUser(\n\t\t\t\t\tmintId(),\n\t\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z') },\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\t'updateUser on an unknown id should reject',\n\t\t\t);\n\n\t\t\tisOurs(\n\t\t\t\terror,\n\t\t\t\tNotFoundError,\n\t\t\t\t'NotFoundError',\n\t\t\t\t'updateUser on an unknown id should reject with NotFoundError, not a version conflict',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.omission',\n\t\tgroup,\n\t\tname: 'omission — the Kratos PUT trap: an update leaves every field it does not name exactly as it was',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord({\n\t\t\t\tfields: { email: 'kept@example.test', plan: 'pro' },\n\t\t\t\tsecondFactor: {\n\t\t\t\t\t...secondFactor,\n\t\t\t\t\tconfirmedAt: at('2026-01-03T00:00:00.000Z'),\n\t\t\t\t\tlastStep: 59_000_000,\n\t\t\t\t},\n\t\t\t\temailVerifiedAt: at('2026-01-02T00:00:00.000Z'),\n\t\t\t});\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst written = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), schemaVersion: '2' },\n\t\t\t\t0,\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\twritten,\n\t\t\t\t{\n\t\t\t\t\t...record,\n\t\t\t\t\tschemaVersion: '2',\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tupdatedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t\t},\n\t\t\t\t'updateUser naming only schemaVersion should leave active, fields, logins, password, secondFactor and emailVerifiedAt untouched',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.passwordSlot',\n\t\tgroup,\n\t\tname: 'keeps a password the patch does not name, and removes one the patch names null',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\n\t\t\tconst kept = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z') },\n\t\t\t\t0,\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tkept.password,\n\t\t\t\trecord.password,\n\t\t\t\t'updateUser not naming password should keep it',\n\t\t\t);\n\n\t\t\tconst removed = await stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-02T00:00:00.000Z'), password: null },\n\t\t\t\t1,\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tremoved.password,\n\t\t\t\t'updateUser with password: null should remove the password',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.secondFactorSlot',\n\t\tgroup,\n\t\tname: 'round-trips a second factor, keeps it when a patch does not name it, and removes it when the patch names null',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord({ secondFactor });\n\t\t\tequal(\n\t\t\t\tawait stores.users.insertUser(record),\n\t\t\t\trecord,\n\t\t\t\t'insertUser should answer a pending second factor as written: confirmedAt and lastStep null',\n\t\t\t);\n\n\t\t\tconst confirmed = {\n\t\t\t\t...secondFactor,\n\t\t\t\tconfirmedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t\tlastStep: 59_000_000,\n\t\t\t};\n\t\t\tequal(\n\t\t\t\t(\n\t\t\t\t\tawait stores.users.updateUser(\n\t\t\t\t\t\trecord.id,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tupdatedAt: at('2026-02-01T00:00:00.000Z'),\n\t\t\t\t\t\t\tsecondFactor: confirmed,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t0,\n\t\t\t\t\t)\n\t\t\t\t).secondFactor,\n\t\t\t\tconfirmed,\n\t\t\t\t'updateUser naming secondFactor should replace it whole',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(\n\t\t\t\t\tawait stores.users.updateUser(\n\t\t\t\t\t\trecord.id,\n\t\t\t\t\t\t{ updatedAt: at('2026-02-02T00:00:00.000Z') },\n\t\t\t\t\t\t1,\n\t\t\t\t\t)\n\t\t\t\t).secondFactor,\n\t\t\t\tconfirmed,\n\t\t\t\t'updateUser not naming secondFactor should keep it',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUser(record.id))?.secondFactor,\n\t\t\t\tconfirmed,\n\t\t\t\t'findUser should answer the second factor as written',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\t(\n\t\t\t\t\tawait stores.users.updateUser(\n\t\t\t\t\t\trecord.id,\n\t\t\t\t\t\t{ updatedAt: at('2026-02-03T00:00:00.000Z'), secondFactor: null },\n\t\t\t\t\t\t2,\n\t\t\t\t\t)\n\t\t\t\t).secondFactor,\n\t\t\t\t'updateUser with secondFactor: null should remove the second factor',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.loginsMove',\n\t\tgroup,\n\t\tname: 'moves logins on update: the old one is free, the new one found, one held elsewhere refused',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tconst other = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tawait stores.users.insertUser(other);\n\t\t\tconst [old] = record.logins;\n\t\t\tconst moved = 'moved@example.test';\n\n\t\t\tawait stores.users.updateUser(\n\t\t\t\trecord.id,\n\t\t\t\t{ updatedAt: at('2026-02-01T00:00:00.000Z'), logins: [moved] },\n\t\t\t\t0,\n\t\t\t);\n\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUserByLogin('user', old ?? ''),\n\t\t\t\t'findUserByLogin for a login the update replaced',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\t(await stores.users.findUserByLogin('user', moved))?.id,\n\t\t\t\trecord.id,\n\t\t\t\t'findUserByLogin for the login the update wrote',\n\t\t\t);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tstores.users.updateUser(\n\t\t\t\t\tother.id,\n\t\t\t\t\t{ updatedAt: at('2026-02-02T00:00:00.000Z'), logins: [moved] },\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\t'updateUser onto a login another user holds should reject',\n\t\t\t);\n\t\t\tisOurs(\n\t\t\t\terror,\n\t\t\t\tStoreConflict,\n\t\t\t\t'StoreConflict',\n\t\t\t\t'updateUser onto a held login should reject with StoreConflict',\n\t\t\t);\n\t\t\tequal(error.code, 'LOGIN_TAKEN', 'updateUser: the conflict code');\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(other.id),\n\t\t\t\tother,\n\t\t\t\t'updateUser refused for a login should have written nothing',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.pagination',\n\t\tgroup,\n\t\tname: 'pages 25 users of one type by 10 in ascending id order, with no gap, no repeat and no other type, and ends on a null cursor',\n\t\tasync run({ stores }) {\n\t\t\tconst records = Array.from({ length: 25 }, () => userRecord());\n\t\t\tconst others = Array.from({ length: 5 }, () =>\n\t\t\t\tuserRecord({ type: 'staff' }),\n\t\t\t);\n\t\t\t// Written newest first, and interleaved with another type, so neither\n\t\t\t// insertion order nor the whole collection is the answer.\n\t\t\tfor (const [index, record] of [...records].reverse().entries()) {\n\t\t\t\tawait stores.users.insertUser(record);\n\t\t\t\tconst other = others[index];\n\t\t\t\tif (other !== undefined) await stores.users.insertUser(other);\n\t\t\t}\n\t\t\tconst expected = records.map((record) => record.id).sort();\n\n\t\t\tconst seen: string[] = [];\n\t\t\tconst sizes: number[] = [];\n\t\t\tlet after: string | null = null;\n\t\t\tfor (let page = 0; page < 10; page += 1) {\n\t\t\t\tconst answer = await stores.users.listUsers({\n\t\t\t\t\ttype: 'user',\n\t\t\t\t\tafter,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t});\n\t\t\t\tseen.push(...answer.items.map((item) => item.id));\n\t\t\t\tsizes.push(answer.items.length);\n\t\t\t\tafter = answer.nextCursor;\n\t\t\t\tif (after === null) break;\n\t\t\t}\n\n\t\t\tequal(sizes, [10, 10, 5], 'listUsers: page sizes for 25 users by 10');\n\t\t\tequal(\n\t\t\t\tseen,\n\t\t\t\texpected,\n\t\t\t\t'listUsers: every id of the type once, in ascending order',\n\t\t\t);\n\n\t\t\t// The cursor need not name a stored user.\n\t\t\tconst between = await stores.users.listUsers({\n\t\t\t\ttype: 'user',\n\t\t\t\tafter: '00000000-0000-7000-8000-000000000000',\n\t\t\t\tlimit: 3,\n\t\t\t});\n\t\t\tok(\n\t\t\t\tbetween.items[0]?.id === expected[0],\n\t\t\t\t'listUsers: a cursor naming no stored user should start after it, not fail',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'users.delete',\n\t\tgroup,\n\t\tname: 'deletes a user and frees their logins, answers false for nobody, and touches no other user',\n\t\tasync run({ stores }) {\n\t\t\tconst record = userRecord();\n\t\t\tconst other = userRecord();\n\t\t\tawait stores.users.insertUser(record);\n\t\t\tawait stores.users.insertUser(other);\n\t\t\tconst [login] = record.logins;\n\n\t\t\tequal(\n\t\t\t\tawait stores.users.deleteUser(record.id),\n\t\t\t\ttrue,\n\t\t\t\t'deleteUser for a stored user should answer true',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUser(record.id),\n\t\t\t\t'findUser after deleteUser',\n\t\t\t);\n\t\t\tisNull(\n\t\t\t\tawait stores.users.findUserByLogin(record.type, login ?? ''),\n\t\t\t\t'findUserByLogin for a deleted user’s login',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.deleteUser(record.id),\n\t\t\t\tfalse,\n\t\t\t\t'deleteUser replayed should answer false, not fail: a deletion interrupted half-way is replayed',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait stores.users.findUser(other.id),\n\t\t\t\tother,\n\t\t\t\t'deleteUser should not touch another user',\n\t\t\t);\n\t\t\t// The login is free for a new account: the index forgot it.\n\t\t\tconst successor = userRecord({ logins: [login ?? ''] });\n\t\t\tequal(\n\t\t\t\tawait stores.users.insertUser(successor),\n\t\t\t\tsuccessor,\n\t\t\t\t'insertUser onto a deleted user’s login should succeed',\n\t\t\t);\n\t\t},\n\t},\n];\n",
|
|
11
11
|
"import { outageCases } from './cases/outage';\nimport { sessionStoreCases } from './cases/sessions';\nimport { tokenStoreCases } from './cases/tokens';\nimport { userStoreCases } from './cases/users';\nimport type {\n\tConformanceCase,\n\tConformanceHarness,\n\tConformanceRunner,\n\tOpenedStores,\n} from './types';\n\n/** Every case, in the order they are described. */\nexport const allCases: readonly ConformanceCase[] = [\n\t...userStoreCases,\n\t...sessionStoreCases,\n\t...tokenStoreCases,\n\t...outageCases,\n];\n\n/** Why a case did not run. A skip is always reported with its reason, never silent. */\nexport const SKIP_REASONS = {\n\tfaults:\n\t\t'faults not provided: the outage invariant is not proven for this adapter',\n\tdeleteExpiredSessions:\n\t\t'stores.sessions does not implement the optional deleteExpiredSessions',\n} as const;\n\n/**\n * Runs one case against one freshly opened set of stores, and closes them,\n * pass or fail. Answers the reason when the case cannot run on these stores.\n *\n * The runner-less layer: a loop over `allCases` calling this is a conformance\n * run with no test framework at all.\n */\nexport async function runCase(\n\tconformanceCase: ConformanceCase,\n\tharness: ConformanceHarness,\n): Promise<{ readonly skipped: string } | { readonly passed: true }> {\n\tconst opened: OpenedStores = await harness.open();\n\n\ttry {\n\t\tif (conformanceCase.needs === 'faults' && opened.faults === undefined) {\n\t\t\treturn { skipped: SKIP_REASONS.faults };\n\t\t}\n\t\tif (\n\t\t\tconformanceCase.needs === 'deleteExpiredSessions' &&\n\t\t\topened.stores.sessions.deleteExpiredSessions === undefined\n\t\t) {\n\t\t\treturn { skipped: SKIP_REASONS.deleteExpiredSessions };\n\t\t}\n\n\t\tawait conformanceCase.run({\n\t\t\tstores: opened.stores,\n\t\t\tfaults: opened.faults ?? null,\n\t\t});\n\t\treturn { passed: true };\n\t} finally {\n\t\tawait opened.close?.();\n\t}\n}\n\n/**\n * Describes the whole suite under the adapter's test runner.\n *\n * ```ts\n * import { describe, it } from 'bun:test';\n * describeJanusStores({ name: 'my adapter', harness, runner: { describe, it } });\n * ```\n *\n * `runner` may be left out under jest, or vitest with `globals: true`: the\n * suite then reads `describe` and `it` from `globalThis`. **Not under `bun\n * test`**, measured: Bun gives a test file `describe` and `it` as bare\n * identifiers, and not as properties of `globalThis` — so pass them.\n *\n * Whether a case can run depends on the stores its harness opens, which is only\n * known once they are open. So every case is described as a test; one that\n * cannot run passes **and says so in its name**, and a run without `faults`\n * is written above the suite, where it cannot be scrolled past.\n */\nexport function describeJanusStores(options: {\n\treadonly name: string;\n\treadonly harness: ConformanceHarness;\n\treadonly runner?: ConformanceRunner;\n\t/** Case ids to skip, each with the reason — reported, never silent. */\n\treadonly skip?: Readonly<Record<string, string>>;\n\t/** Declared up front, so the report can say so before the first case runs. */\n\treadonly faults?: boolean;\n}): void {\n\tdescribeSuite({\n\t\ttitle: `${options.name} — @nxgt/janus conformance`,\n\t\tcases: allCases,\n\t\trun: (conformanceCase) => runCase(conformanceCase, options.harness),\n\t\trunner: options.runner ?? fromGlobals('describeJanusStores'),\n\t\tskip: options.skip ?? {},\n\t\tfaults: options.faults,\n\t});\n}\n\n/** What any suite's case declares, as far as describing it goes. */\ninterface DescribedCase {\n\treadonly id: string;\n\treadonly group: string;\n\treadonly name: string;\n\treadonly needs?: string;\n}\n\n/**\n * Describes one suite's cases, grouped, under a runner. Shared by every suite\n * this module exports, so a skip, an unproven outage and a missing runner read\n * the same in each.\n */\nexport function describeSuite<C extends DescribedCase>(options: {\n\treadonly title: string;\n\treadonly cases: readonly C[];\n\treadonly run: (\n\t\tconformanceCase: C,\n\t) => Promise<{ readonly skipped: string } | { readonly passed: true }>;\n\treadonly runner: ConformanceRunner;\n\treadonly skip: Readonly<Record<string, string>>;\n\treadonly faults: boolean | undefined;\n}): void {\n\tconst { runner, skip, cases } = options;\n\tconst groups = [...new Set(cases.map((c) => c.group))];\n\n\tconst title =\n\t\toptions.faults === false\n\t\t\t? `${options.title} (WITHOUT faults: ${SKIP_REASONS.faults})`\n\t\t\t: options.title;\n\n\trunner.describe(title, () => {\n\t\tfor (const group of groups) {\n\t\t\trunner.describe(group, () => {\n\t\t\t\tfor (const conformanceCase of cases.filter((c) => c.group === group)) {\n\t\t\t\t\tconst reason = skip[conformanceCase.id];\n\t\t\t\t\tif (reason !== undefined) {\n\t\t\t\t\t\trunner.it.skip(\n\t\t\t\t\t\t\t`${conformanceCase.name} — skipped: ${reason}`,\n\t\t\t\t\t\t\tasync () => {},\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (conformanceCase.needs === 'faults' && options.faults === false) {\n\t\t\t\t\t\trunner.it.skip(\n\t\t\t\t\t\t\t`${conformanceCase.name} — skipped: ${SKIP_REASONS.faults}`,\n\t\t\t\t\t\t\tasync () => {},\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\trunner.it(conformanceCase.name, async () => {\n\t\t\t\t\t\tconst outcome = await options.run(conformanceCase);\n\t\t\t\t\t\tif ('skipped' in outcome) {\n\t\t\t\t\t\t\tprocess.emitWarning(\n\t\t\t\t\t\t\t\t`${conformanceCase.id} skipped: ${outcome.skipped}`,\n\t\t\t\t\t\t\t\t{ code: 'JANUS_CONFORMANCE_SKIPPED' },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t});\n}\n\nexport function fromGlobals(where: string): ConformanceRunner {\n\tconst globals = globalThis as {\n\t\tdescribe?: ConformanceRunner['describe'];\n\t\tit?: ConformanceRunner['it'];\n\t};\n\n\tif (\n\t\ttypeof globals.describe !== 'function' ||\n\t\ttypeof globals.it !== 'function'\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: no test runner on globalThis — pass runner: { describe, it } (under bun test: import them from 'bun:test')`,\n\t\t);\n\t}\n\n\treturn { describe: globals.describe, it: globals.it };\n}\n",
|
|
12
12
|
"import { createMemoryStores } from '../auth/port/memory';\nimport type { JanusStores } from '../auth/port/types';\nimport { StoreFailure } from '../errors/janus-error';\nimport type { ConformanceHarness, StoreFaults } from './types';\n\n/**\n * The harness for the reference store: what `self.spec.ts` runs the suite\n * against, and **the example an adapter author copies**.\n *\n * Its faults are a wrapper, because an in-memory map has no network to cut —\n * which is exactly why the doc on `StoreFaults` says an adapter must not do\n * this: a wrapper proves the wrapper. For the reference store there is nothing\n * underneath to prove, and it fails the way a correct adapter does, with\n * `StoreFailure`.\n */\nexport function referenceHarness(): ConformanceHarness {\n\treturn {\n\t\tasync open() {\n\t\t\tconst failing = new Set<string>();\n\t\t\tconst stores = withFaults(createMemoryStores(), failing);\n\n\t\t\tconst faults: StoreFaults = {\n\t\t\t\tasync fail(slot, method) {\n\t\t\t\t\tfailing.add(`${slot}.${method}`);\n\t\t\t\t},\n\t\t\t};\n\n\t\t\treturn { stores, faults };\n\t\t},\n\t};\n}\n\nfunction withFaults(\n\tstores: JanusStores,\n\tfailing: ReadonlySet<string>,\n): JanusStores {\n\tconst wrap = <S extends object>(slot: string, store: S): S => {\n\t\tconst wrapped: Record<string, unknown> = {};\n\t\tfor (const [method, fn] of Object.entries(store)) {\n\t\t\twrapped[method] = (...args: unknown[]) => {\n\t\t\t\tif (failing.has(`${slot}.${method}`)) {\n\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\tnew StoreFailure(`${slot}.${method}: the store could not answer`, {\n\t\t\t\t\t\t\tslot: slot as keyof JanusStores,\n\t\t\t\t\t\t\toperation: method,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn (fn as (...a: unknown[]) => unknown).apply(store, args);\n\t\t\t};\n\t\t}\n\t\treturn wrapped as S;\n\t};\n\n\treturn {\n\t\tusers: wrap('users', stores.users),\n\t\tsessions: wrap('sessions', stores.sessions),\n\t\ttokens: wrap('tokens', stores.tokens),\n\t};\n}\n",
|
|
13
|
-
"/**\n * The conformance suite of the relation store port — what an adapter author\n * runs against `RelationStore`, as `describeJanusStores` is run against the\n * user port.\n *\n * ```ts\n * import { describe, it } from 'bun:test';\n * import { describeRelationStores } from '@nxgt/janus/conformance';\n *\n * describeRelationStores({ name: 'my adapter', harness, runner: { describe, it } });\n * ```\n */\n\nimport { JanusError, StoreFailure } from '../errors/janus-error';\nimport { mintId } from '../ids/id';\nimport { createMemoryRelations } from '../permissions/port/memory';\nimport type { RelationStore } from '../permissions/port/types';\nimport { formatSubject } from '../subjects/notation';\nimport type {\n\tEntity,\n\tRelationTuple,\n\tSubject,\n\tSubjectSet,\n} from '../subjects/subject';\nimport { equal, isOurs, ok, rejects } from './assert';\nimport { describeSuite, fromGlobals, SKIP_REASONS } from './describe';\nimport type { ConformanceRunner } from './types';\n\n/** A method of the relation store. */\nexport type RelationMethod = keyof RelationStore & string;\n\n/** How the suite makes one relation store method fail, for the outage cases. */\nexport interface RelationFaults {\n\t/**\n\t * Fails `method`, and only it: the other methods keep answering, because\n\t * `outage.write` reads the store back to prove the rejected write changed\n\t * nothing.\n\t */\n\tfail(method: RelationMethod): Promise<void>;\n}\n\nexport interface OpenedRelations {\n\t/** Empty: every case writes what it reads. */\n\treadonly store: RelationStore;\n\treadonly faults?: RelationFaults;\n\tclose?(): Promise<void>;\n}\n\nexport interface RelationHarness {\n\t/** Called once per case, so no case sees another's tuples. */\n\topen(): Promise<OpenedRelations>;\n}\n\nexport interface RelationContext {\n\treadonly store: RelationStore;\n\treadonly faults: RelationFaults | null;\n}\n\nexport interface RelationCase {\n\treadonly id: string;\n\treadonly group: 'relations' | 'outage';\n\treadonly name: string;\n\treadonly needs?: 'faults';\n\trun(context: RelationContext): Promise<void>;\n}\n\n// ─── Fixtures ─────────────────────────────────────────────────────────────\n\nconst entity = (type: string, id: string = mintId()): Entity => ({ type, id });\nconst set = (type: string, id: string, relation: string): SubjectSet => ({\n\ttype,\n\tid,\n\trelation,\n});\nconst tuple = (\n\tobject: Entity,\n\trelation: string,\n\tsubject: Subject,\n): RelationTuple => ({ object, relation, subject });\n\n/** Order-free, so a store may answer in any order. */\nconst sorted = (subjects: readonly Subject[]) =>\n\tsubjects.map(formatSubject).sort();\n\n// ─── The cases ────────────────────────────────────────────────────────────\n\nconst group = 'relations';\n\nexport const relationStoreCases: readonly RelationCase[] = [\n\t{\n\t\tid: 'relations.roundTrip',\n\t\tgroup,\n\t\tname: 'holds exactly the tuples written, byte for byte: staff:u1 is not patient:u1, and a set is not its entity',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record', 'R:1 — ÿ');\n\t\t\tconst direct = tuple(record, 'viewer', entity('staff', 'u1'));\n\t\t\tconst inherited = tuple(record, 'viewer', set('team', 't1', 'member'));\n\t\t\tawait store.write({ add: [direct, inherited] });\n\n\t\t\tequal(await store.has(direct), true, 'has for a written tuple');\n\t\t\tequal(await store.has(inherited), true, 'has for a written subject set');\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', entity('patient', 'u1'))),\n\t\t\t\tfalse,\n\t\t\t\t'has for the same id under another subject type: types are part of the subject',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', entity('team', 't1'))),\n\t\t\t\tfalse,\n\t\t\t\t'has for the entity of a stored subject set: a set is not its entity',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(\n\t\t\t\t\ttuple(entity('record', 'r:1 — ÿ'), 'viewer', direct.subject),\n\t\t\t\t),\n\t\t\t\tfalse,\n\t\t\t\t'has for an id differing in one character: ids are compared as written',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.edgeCharacters',\n\t\tgroup,\n\t\tname: 'round-trips every character the core lets through in an id: control characters, U+FFFF and a surrogate pair',\n\t\tasync run({ store }) {\n\t\t\t// The core refuses a NUL and a lone surrogate before a store is\n\t\t\t// asked; everything else must come back exactly as written.\n\t\t\tconst edge = 'a\\u0001\\u001f\\u007f\\uFFFF 😀 z';\n\t\t\tconst record = entity('record', edge);\n\t\t\tconst written = tuple(record, 'viewer', set('team', edge, 'member'));\n\t\t\tconst direct = tuple(record, 'viewer', entity('staff', edge));\n\t\t\tawait store.write({ add: [written, direct] });\n\n\t\t\tequal(await store.has(written), true, 'has for ids of edge characters');\n\t\t\t// Read back through every index: an adapter that mangles an id the\n\t\t\t// same way on write and on lookup still fails here.\n\t\t\tequal(\n\t\t\t\tawait store.findSubjectSets(record, 'viewer'),\n\t\t\t\t[set('team', edge, 'member')],\n\t\t\t\t'findSubjectSets answers the set id as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findEntities(record, 'viewer'),\n\t\t\t\t[entity('staff', edge)],\n\t\t\t\t'findEntities answers the entity id as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: direct.subject,\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [edge], nextCursor: null },\n\t\t\t\t'findObjects answers the object id as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(\n\t\t\t\t\ttuple(\n\t\t\t\t\t\tentity('record', edge.replace('😀', '😁')),\n\t\t\t\t\t\t'viewer',\n\t\t\t\t\t\twritten.subject,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tfalse,\n\t\t\t\t'has for an id differing in one astral character',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.undefinedRelation',\n\t\tgroup,\n\t\tname: 'reads a subject whose relation is undefined as its entity, not as a set',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst staff = entity('staff');\n\t\t\t// A caller that spreads an optional field hands the port this shape.\n\t\t\tconst spread = { ...staff, relation: undefined } as unknown as Subject;\n\t\t\tawait store.write({ add: [tuple(record, 'viewer', spread)] });\n\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', staff)),\n\t\t\t\ttrue,\n\t\t\t\t'has for the entity, after writing it with relation: undefined',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findEntities(record, 'viewer'),\n\t\t\t\t[staff],\n\t\t\t\t'findEntities: the tuple names an entity',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findSubjectSets(record, 'viewer'),\n\t\t\t\t[],\n\t\t\t\t'findSubjectSets: relation: undefined is no set',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: spread,\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [record.id], nextCursor: null },\n\t\t\t\t'findObjects for the subject written with relation: undefined',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: set('staff', staff.id, 'x'),\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [], nextCursor: null },\n\t\t\t\t'findObjects for a set of that entity: the entity is not a set',\n\t\t\t);\n\t\t\tawait store.write({ remove: [tuple(record, 'viewer', spread)] });\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', staff)),\n\t\t\t\tfalse,\n\t\t\t\t'has after removing it with relation: undefined',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.absence',\n\t\tgroup,\n\t\tname: 'answers false, [], an empty page and 0 for an empty store — never null or undefined',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst staff = entity('staff');\n\n\t\t\tequal(await store.has(tuple(record, 'viewer', staff)), false, 'has');\n\t\t\tequal(\n\t\t\t\tawait store.findSubjectSets(record, 'viewer'),\n\t\t\t\t[],\n\t\t\t\t'findSubjectSets',\n\t\t\t);\n\t\t\tequal(await store.findEntities(record, 'viewer'), [], 'findEntities');\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: staff,\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [], nextCursor: null },\n\t\t\t\t'findObjects on an empty store',\n\t\t\t);\n\t\t\tequal(await store.deleteEntity(staff), 0, 'deleteEntity');\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.idempotentWrite',\n\t\tgroup,\n\t\tname: 'is idempotent under retry: adding a stored tuple and removing an absent one write nothing',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst viewer = tuple(record, 'viewer', entity('team'));\n\t\t\tawait store.write({ add: [viewer] });\n\t\t\tawait store.write({ add: [viewer] });\n\t\t\tawait store.write({ remove: [tuple(record, 'viewer', entity('team'))] });\n\n\t\t\tequal(\n\t\t\t\t(await store.findEntities(record, 'viewer')).length,\n\t\t\t\t1,\n\t\t\t\t'findEntities after the same tuple was added twice',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.uniqueness',\n\t\tgroup,\n\t\tname: 'keeps one tuple when twenty concurrent writes add it: uniqueness is a constraint, not a read',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst parent = entity('folder');\n\t\t\tawait Promise.all(\n\t\t\t\tArray.from({ length: 20 }, () =>\n\t\t\t\t\tstore.write({ add: [tuple(record, 'parent', parent)] }),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\tawait store.findEntities(record, 'parent'),\n\t\t\t\t[parent],\n\t\t\t\t'findEntities after twenty concurrent adds of one tuple',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.removeThenAdd',\n\t\tgroup,\n\t\tname: 'applies one write’s removals and additions together',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst before = tuple(record, 'owner', entity('staff', 'a'));\n\t\t\tconst after = tuple(record, 'owner', entity('staff', 'b'));\n\t\t\tawait store.write({ add: [before] });\n\n\t\t\tawait store.write({ remove: [before], add: [after] });\n\n\t\t\tequal(await store.has(before), false, 'the removed tuple');\n\t\t\tequal(await store.has(after), true, 'the added tuple');\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.oneHop',\n\t\tgroup,\n\t\tname: 'answers one hop: the subject sets and the entities holding one relation on one object, and nothing else',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst staff = entity('staff');\n\t\t\tconst team = entity('team');\n\t\t\tconst members = set('team', team.id, 'member');\n\t\t\tconst leads = set('team', team.id, 'lead');\n\t\t\tawait store.write({\n\t\t\t\tadd: [\n\t\t\t\t\ttuple(record, 'viewer', staff),\n\t\t\t\t\ttuple(record, 'viewer', team),\n\t\t\t\t\ttuple(record, 'viewer', members),\n\t\t\t\t\ttuple(record, 'viewer', leads),\n\t\t\t\t\t// Another relation, and another object: neither is an answer.\n\t\t\t\t\ttuple(record, 'editor', set('team', team.id, 'admin')),\n\t\t\t\t\ttuple(entity('record'), 'viewer', set('team', 'other', 'member')),\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tsorted(await store.findSubjectSets(record, 'viewer')),\n\t\t\t\tsorted([members, leads]),\n\t\t\t\t'findSubjectSets: the sets only, of this object and this relation',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tsorted(await store.findEntities(record, 'viewer')),\n\t\t\t\tsorted([staff, team]),\n\t\t\t\t'findEntities: the single entities only, of this object and this relation',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.objects',\n\t\tgroup,\n\t\tname: 'pages 25 objects by 10 in ascending id order, for one type, relation and exact subject, and ends on a null cursor',\n\t\tasync run({ store }) {\n\t\t\tconst staff = entity('staff');\n\t\t\tconst ids = Array.from(\n\t\t\t\t{ length: 25 },\n\t\t\t\t(_, n) => `r${String(n).padStart(2, '0')}`,\n\t\t\t);\n\t\t\t// Written newest first, beside noise the answer must leave out.\n\t\t\tfor (const id of [...ids].reverse()) {\n\t\t\t\tawait store.write({\n\t\t\t\t\tadd: [\n\t\t\t\t\t\ttuple(entity('record', id), 'viewer', staff),\n\t\t\t\t\t\ttuple(entity('record', `${id}x`), 'editor', staff),\n\t\t\t\t\t\ttuple(entity('folder', id), 'viewer', staff),\n\t\t\t\t\t\ttuple(\n\t\t\t\t\t\t\tentity('record', `${id}y`),\n\t\t\t\t\t\t\t'viewer',\n\t\t\t\t\t\t\tset('staff', staff.id, 'x'),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst seen: string[] = [];\n\t\t\tconst sizes: number[] = [];\n\t\t\tlet after: string | null = null;\n\t\t\tfor (let page = 0; page < 10; page += 1) {\n\t\t\t\tconst answer = await store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: staff,\n\t\t\t\t\tafter,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t});\n\t\t\t\tseen.push(...answer.items);\n\t\t\t\tsizes.push(answer.items.length);\n\t\t\t\tafter = answer.nextCursor;\n\t\t\t\tif (after === null) break;\n\t\t\t}\n\n\t\t\tequal(sizes, [10, 10, 5], 'findObjects: page sizes for 25 objects by 10');\n\t\t\tequal(seen, ids, 'findObjects: every id once, in ascending order');\n\t\t\tconst between = await store.findObjects({\n\t\t\t\ttype: 'record',\n\t\t\t\trelation: 'viewer',\n\t\t\t\tsubject: staff,\n\t\t\t\tafter: 'r04a',\n\t\t\t\tlimit: 2,\n\t\t\t});\n\t\t\tequal(\n\t\t\t\tbetween.items,\n\t\t\t\t['r05', 'r06'],\n\t\t\t\t'findObjects: a cursor naming no stored object should start after it',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.deleteEntity',\n\t\tgroup,\n\t\tname: 'deletes every tuple naming an entity — as object, as subject, as a set’s entity — and counts them',\n\t\tasync run({ store }) {\n\t\t\tconst team = entity('team');\n\t\t\tconst other = entity('team');\n\t\t\tconst kept = [\n\t\t\t\ttuple(other, 'member', entity('staff')),\n\t\t\t\ttuple(entity('record'), 'viewer', set('team', other.id, 'member')),\n\t\t\t];\n\t\t\tawait store.write({\n\t\t\t\tadd: [\n\t\t\t\t\ttuple(team, 'member', entity('staff')),\n\t\t\t\t\ttuple(entity('record'), 'team', team),\n\t\t\t\t\ttuple(entity('record'), 'viewer', set('team', team.id, 'member')),\n\t\t\t\t\t...kept,\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tawait store.deleteEntity(team),\n\t\t\t\t3,\n\t\t\t\t'deleteEntity: how many it deleted',\n\t\t\t);\n\t\t\tfor (const survivor of kept) {\n\t\t\t\tequal(\n\t\t\t\t\tawait store.has(survivor),\n\t\t\t\t\ttrue,\n\t\t\t\t\t'deleteEntity should not touch another entity',\n\t\t\t\t);\n\t\t\t}\n\t\t\tequal(\n\t\t\t\tawait store.deleteEntity(team),\n\t\t\t\t0,\n\t\t\t\t'deleteEntity replayed answers 0, not a failure',\n\t\t\t);\n\t\t},\n\t},\n];\n\n// ─── Outages ──────────────────────────────────────────────────────────────\n\n/**\n * For each method, a store that cannot answer must **reject** — never\n * `false`, `[]`, an empty page or `0`. A relation store that answers `false`\n * for an outage denies everybody everything and says nothing; one that\n * resolves a write it did not make leaves a revoked access standing.\n */\nfunction outage(\n\tmethod: RelationMethod,\n\tcall: (store: RelationStore) => Promise<unknown>,\n\tafter?: (store: RelationStore) => Promise<void>,\n): RelationCase {\n\treturn {\n\t\tid: `outage.${method}`,\n\t\tgroup: 'outage',\n\t\tname: `relations.${method} rejects when the store cannot answer — never false, [], an empty page or 0`,\n\t\tneeds: 'faults',\n\t\tasync run({ store, faults }) {\n\t\t\tawait store.write({ add: [seeded] });\n\t\t\tawait faults?.fail(method);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tcall(store),\n\t\t\t\t`relations.${method} under an outage should reject`,\n\t\t\t);\n\t\t\tif (\n\t\t\t\terror instanceof JanusError ||\n\t\t\t\t(error as { name?: unknown })?.name === 'StoreFailure'\n\t\t\t) {\n\t\t\t\tisOurs(error, StoreFailure, `relations.${method} under an outage`);\n\t\t\t}\n\t\t\tok(\n\t\t\t\t(error as { code?: unknown } | null)?.code !== 'NOT_FOUND',\n\t\t\t\t`relations.${method} under an outage rejected with NOT_FOUND: an outage is never an absence`,\n\t\t\t);\n\t\t\tawait after?.(store);\n\t\t},\n\t};\n}\n\nconst seededObject: Entity = { type: 'record', id: 'seeded' };\nconst seededSubject: Entity = { type: 'staff', id: 'seeded' };\nconst seeded = tuple(seededObject, 'viewer', seededSubject);\n\nexport const relationOutageCases: readonly RelationCase[] = [\n\toutage(\n\t\t'write',\n\t\t(store) =>\n\t\t\tstore.write({\n\t\t\t\tremove: [seeded],\n\t\t\t\tadd: [tuple(seededObject, 'owner', seededSubject)],\n\t\t\t}),\n\t\t// A rejected write is one that did nothing: all of it, or none of it.\n\t\tasync (store) => {\n\t\t\tequal(\n\t\t\t\tawait store.has(seeded),\n\t\t\t\ttrue,\n\t\t\t\t'relations.write rejected, and still removed a tuple: a write is all or nothing',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(seededObject, 'owner', seededSubject)),\n\t\t\t\tfalse,\n\t\t\t\t'relations.write rejected, and still added a tuple: a write is all or nothing',\n\t\t\t);\n\t\t},\n\t),\n\toutage('has', (store) => store.has(seeded)),\n\toutage('findSubjectSets', (store) =>\n\t\tstore.findSubjectSets(seededObject, 'viewer'),\n\t),\n\toutage('findEntities', (store) => store.findEntities(seededObject, 'viewer')),\n\toutage('findObjects', (store) =>\n\t\tstore.findObjects({\n\t\t\ttype: 'record',\n\t\t\trelation: 'viewer',\n\t\t\tsubject: seededSubject,\n\t\t\tafter: null,\n\t\t\tlimit: 10,\n\t\t}),\n\t),\n\toutage('deleteEntity', (store) => store.deleteEntity(seededSubject)),\n];\n\nexport const allRelationCases: readonly RelationCase[] = [\n\t...relationStoreCases,\n\t...relationOutageCases,\n];\n\n// ─── Running them ─────────────────────────────────────────────────────────\n\n/** Runs one case against a freshly opened store, and closes it, pass or fail. */\nexport async function runRelationCase(\n\trelationCase: RelationCase,\n\tharness: RelationHarness,\n): Promise<{ readonly skipped: string } | { readonly passed: true }> {\n\tconst opened = await harness.open();\n\n\ttry {\n\t\tif (relationCase.needs === 'faults' && opened.faults === undefined) {\n\t\t\treturn { skipped: SKIP_REASONS.faults };\n\t\t}\n\t\tawait relationCase.run({\n\t\t\tstore: opened.store,\n\t\t\tfaults: opened.faults ?? null,\n\t\t});\n\t\treturn { passed: true };\n\t} finally {\n\t\tawait opened.close?.();\n\t}\n}\n\n/** Describes the whole relation suite under the adapter's test runner. */\nexport function describeRelationStores(options: {\n\treadonly name: string;\n\treadonly harness: RelationHarness;\n\treadonly runner?: ConformanceRunner;\n\t/** Case ids to skip, each with the reason — reported, never silent. */\n\treadonly skip?: Readonly<Record<string, string>>;\n\treadonly faults?: boolean;\n}): void {\n\tdescribeSuite({\n\t\ttitle: `${options.name} — @nxgt/janus relation conformance`,\n\t\tcases: allRelationCases,\n\t\trun: (relationCase) => runRelationCase(relationCase, options.harness),\n\t\trunner: options.runner ?? fromGlobals('describeRelationStores'),\n\t\tskip: options.skip ?? {},\n\t\tfaults: options.faults,\n\t});\n}\n\n/** The reference store, with faults: what the suite proves itself against. */\nexport function referenceRelationHarness(): RelationHarness {\n\treturn {\n\t\tasync open() {\n\t\t\tconst failing = new Set<string>();\n\t\t\tconst inner = createMemoryRelations();\n\t\t\tconst store = Object.fromEntries(\n\t\t\t\tObject.entries(inner).map(([method, fn]) => [\n\t\t\t\t\tmethod,\n\t\t\t\t\t(...args: unknown[]) =>\n\t\t\t\t\t\tfailing.has(method)\n\t\t\t\t\t\t\t? Promise.reject(\n\t\t\t\t\t\t\t\t\tnew StoreFailure(\n\t\t\t\t\t\t\t\t\t\t`relations.${method}: the store could not answer`,\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tslot: 'relations',\n\t\t\t\t\t\t\t\t\t\t\toperation: method,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: (fn as (...a: unknown[]) => unknown).apply(inner, args),\n\t\t\t\t]),\n\t\t\t) as unknown as RelationStore;\n\n\t\t\treturn {\n\t\t\t\tstore,\n\t\t\t\tfaults: {\n\t\t\t\t\tasync fail(method) {\n\t\t\t\t\t\tfailing.add(method);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n"
|
|
13
|
+
"/**\n * The conformance suite of the relation store port — what an adapter author\n * runs against `RelationStore`, as `describeJanusStores` is run against the\n * user port.\n *\n * ```ts\n * import { describe, it } from 'bun:test';\n * import { describeRelationStores } from '@nxgt/janus/conformance';\n *\n * describeRelationStores({ name: 'my adapter', harness, runner: { describe, it } });\n * ```\n */\n\nimport { JanusError, StoreFailure } from '../errors/janus-error';\nimport { mintId } from '../ids/id';\nimport { createMemoryRelations } from '../permissions/port/memory';\nimport type { RelationStore } from '../permissions/port/types';\nimport { formatSubject } from '../subjects/notation';\nimport type {\n\tEntity,\n\tRelationTuple,\n\tSubject,\n\tSubjectSet,\n} from '../subjects/subject';\nimport { equal, isOurs, ok, rejects } from './assert';\nimport { describeSuite, fromGlobals, SKIP_REASONS } from './describe';\nimport type { ConformanceRunner } from './types';\n\n/** A method of the relation store. */\nexport type RelationMethod = keyof RelationStore & string;\n\n/** How the suite makes one relation store method fail, for the outage cases. */\nexport interface RelationFaults {\n\t/**\n\t * Fails `method`, and only it: the other methods keep answering, because\n\t * `outage.write` reads the store back to prove the rejected write changed\n\t * nothing.\n\t */\n\tfail(method: RelationMethod): Promise<void>;\n}\n\nexport interface OpenedRelations {\n\t/** Empty: every case writes what it reads. */\n\treadonly store: RelationStore;\n\treadonly faults?: RelationFaults;\n\tclose?(): Promise<void>;\n}\n\nexport interface RelationHarness {\n\t/** Called once per case, so no case sees another's tuples. */\n\topen(): Promise<OpenedRelations>;\n}\n\nexport interface RelationContext {\n\treadonly store: RelationStore;\n\treadonly faults: RelationFaults | null;\n}\n\nexport interface RelationCase {\n\treadonly id: string;\n\treadonly group: 'relations' | 'outage';\n\treadonly name: string;\n\treadonly needs?: 'faults';\n\trun(context: RelationContext): Promise<void>;\n}\n\n// ─── Fixtures ─────────────────────────────────────────────────────────────\n\nconst entity = (type: string, id: string = mintId()): Entity => ({ type, id });\nconst set = (type: string, id: string, relation: string): SubjectSet => ({\n\ttype,\n\tid,\n\trelation,\n});\nconst tuple = (\n\tobject: Entity,\n\trelation: string,\n\tsubject: Subject,\n): RelationTuple => ({ object, relation, subject });\n\n/** Order-free, so a store may answer in any order. */\nconst sorted = (subjects: readonly Subject[]) =>\n\tsubjects.map(formatSubject).sort();\n\n// ─── The cases ────────────────────────────────────────────────────────────\n\nconst group = 'relations';\n\nexport const relationStoreCases: readonly RelationCase[] = [\n\t{\n\t\tid: 'relations.roundTrip',\n\t\tgroup,\n\t\tname: 'holds exactly the tuples written, byte for byte: staff:u1 is not patient:u1, and a set is not its entity',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record', 'R:1 — ÿ');\n\t\t\tconst direct = tuple(record, 'viewer', entity('staff', 'u1'));\n\t\t\tconst inherited = tuple(record, 'viewer', set('team', 't1', 'member'));\n\t\t\tawait store.write({ add: [direct, inherited] });\n\n\t\t\tequal(await store.has(direct), true, 'has for a written tuple');\n\t\t\tequal(await store.has(inherited), true, 'has for a written subject set');\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', entity('patient', 'u1'))),\n\t\t\t\tfalse,\n\t\t\t\t'has for the same id under another subject type: types are part of the subject',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', entity('team', 't1'))),\n\t\t\t\tfalse,\n\t\t\t\t'has for the entity of a stored subject set: a set is not its entity',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(\n\t\t\t\t\ttuple(entity('record', 'r:1 — ÿ'), 'viewer', direct.subject),\n\t\t\t\t),\n\t\t\t\tfalse,\n\t\t\t\t'has for an id differing in one character: ids are compared as written',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.edgeCharacters',\n\t\tgroup,\n\t\tname: 'round-trips every character the core lets through in an id: control characters, U+FFFF and a surrogate pair',\n\t\tasync run({ store }) {\n\t\t\t// The core refuses a NUL and a lone surrogate before a store is\n\t\t\t// asked; everything else must come back exactly as written.\n\t\t\tconst edge = 'a\\u0001\\u001f\\u007f\\uFFFF 😀 z';\n\t\t\tconst record = entity('record', edge);\n\t\t\tconst written = tuple(record, 'viewer', set('team', edge, 'member'));\n\t\t\tconst direct = tuple(record, 'viewer', entity('staff', edge));\n\t\t\tawait store.write({ add: [written, direct] });\n\n\t\t\tequal(await store.has(written), true, 'has for ids of edge characters');\n\t\t\t// Read back through every index: an adapter that mangles an id the\n\t\t\t// same way on write and on lookup still fails here.\n\t\t\tequal(\n\t\t\t\tawait store.findSubjectSets(record, 'viewer'),\n\t\t\t\t[set('team', edge, 'member')],\n\t\t\t\t'findSubjectSets answers the set id as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findEntities(record, 'viewer'),\n\t\t\t\t[entity('staff', edge)],\n\t\t\t\t'findEntities answers the entity id as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: direct.subject,\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [edge], nextCursor: null },\n\t\t\t\t'findObjects answers the object id as written',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(\n\t\t\t\t\ttuple(\n\t\t\t\t\t\tentity('record', edge.replace('😀', '😁')),\n\t\t\t\t\t\t'viewer',\n\t\t\t\t\t\twritten.subject,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tfalse,\n\t\t\t\t'has for an id differing in one astral character',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.undefinedRelation',\n\t\tgroup,\n\t\tname: 'reads a subject whose relation is undefined as its entity, not as a set',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst staff = entity('staff');\n\t\t\t// A caller that spreads an optional field hands the port this shape.\n\t\t\tconst spread = { ...staff, relation: undefined } as unknown as Subject;\n\t\t\tawait store.write({ add: [tuple(record, 'viewer', spread)] });\n\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', staff)),\n\t\t\t\ttrue,\n\t\t\t\t'has for the entity, after writing it with relation: undefined',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findEntities(record, 'viewer'),\n\t\t\t\t[staff],\n\t\t\t\t'findEntities: the tuple names an entity',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findSubjectSets(record, 'viewer'),\n\t\t\t\t[],\n\t\t\t\t'findSubjectSets: relation: undefined is no set',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: spread,\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [record.id], nextCursor: null },\n\t\t\t\t'findObjects for the subject written with relation: undefined',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: set('staff', staff.id, 'x'),\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [], nextCursor: null },\n\t\t\t\t'findObjects for a set of that entity: the entity is not a set',\n\t\t\t);\n\t\t\tawait store.write({ remove: [tuple(record, 'viewer', spread)] });\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(record, 'viewer', staff)),\n\t\t\t\tfalse,\n\t\t\t\t'has after removing it with relation: undefined',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.absence',\n\t\tgroup,\n\t\tname: 'answers false, [], an empty page and 0 for an empty store — never null or undefined',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst staff = entity('staff');\n\n\t\t\tequal(await store.has(tuple(record, 'viewer', staff)), false, 'has');\n\t\t\tequal(\n\t\t\t\tawait store.findSubjectSets(record, 'viewer'),\n\t\t\t\t[],\n\t\t\t\t'findSubjectSets',\n\t\t\t);\n\t\t\tequal(await store.findEntities(record, 'viewer'), [], 'findEntities');\n\t\t\tequal(\n\t\t\t\tawait store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: staff,\n\t\t\t\t\tafter: null,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t}),\n\t\t\t\t{ items: [], nextCursor: null },\n\t\t\t\t'findObjects on an empty store',\n\t\t\t);\n\t\t\tequal(await store.deleteEntity(staff), 0, 'deleteEntity');\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.idempotentWrite',\n\t\tgroup,\n\t\tname: 'is idempotent under retry: adding a stored tuple and removing an absent one write nothing',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst viewer = tuple(record, 'viewer', entity('team'));\n\t\t\tawait store.write({ add: [viewer] });\n\t\t\tawait store.write({ add: [viewer] });\n\t\t\tawait store.write({ remove: [tuple(record, 'viewer', entity('team'))] });\n\n\t\t\tequal(\n\t\t\t\t(await store.findEntities(record, 'viewer')).length,\n\t\t\t\t1,\n\t\t\t\t'findEntities after the same tuple was added twice',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.uniqueness',\n\t\tgroup,\n\t\tname: 'keeps one tuple when twenty concurrent writes add it: uniqueness is a constraint, not a read',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst parent = entity('folder');\n\t\t\tawait Promise.all(\n\t\t\t\tArray.from({ length: 20 }, () =>\n\t\t\t\t\tstore.write({ add: [tuple(record, 'parent', parent)] }),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tequal(\n\t\t\t\tawait store.findEntities(record, 'parent'),\n\t\t\t\t[parent],\n\t\t\t\t'findEntities after twenty concurrent adds of one tuple',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.removeThenAdd',\n\t\tgroup,\n\t\tname: 'applies one write’s removals and additions together',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst before = tuple(record, 'owner', entity('staff', 'a'));\n\t\t\tconst after = tuple(record, 'owner', entity('staff', 'b'));\n\t\t\tawait store.write({ add: [before] });\n\n\t\t\tawait store.write({ remove: [before], add: [after] });\n\n\t\t\tequal(await store.has(before), false, 'the removed tuple');\n\t\t\tequal(await store.has(after), true, 'the added tuple');\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.oneHop',\n\t\tgroup,\n\t\tname: 'answers one hop: the subject sets and the entities holding one relation on one object, and nothing else',\n\t\tasync run({ store }) {\n\t\t\tconst record = entity('record');\n\t\t\tconst staff = entity('staff');\n\t\t\tconst team = entity('team');\n\t\t\tconst members = set('team', team.id, 'member');\n\t\t\tconst leads = set('team', team.id, 'lead');\n\t\t\tawait store.write({\n\t\t\t\tadd: [\n\t\t\t\t\ttuple(record, 'viewer', staff),\n\t\t\t\t\ttuple(record, 'viewer', team),\n\t\t\t\t\ttuple(record, 'viewer', members),\n\t\t\t\t\ttuple(record, 'viewer', leads),\n\t\t\t\t\t// Another relation, and another object: neither is an answer.\n\t\t\t\t\ttuple(record, 'editor', set('team', team.id, 'admin')),\n\t\t\t\t\ttuple(entity('record'), 'viewer', set('team', 'other', 'member')),\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tsorted(await store.findSubjectSets(record, 'viewer')),\n\t\t\t\tsorted([members, leads]),\n\t\t\t\t'findSubjectSets: the sets only, of this object and this relation',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tsorted(await store.findEntities(record, 'viewer')),\n\t\t\t\tsorted([staff, team]),\n\t\t\t\t'findEntities: the single entities only, of this object and this relation',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.objects',\n\t\tgroup,\n\t\tname: 'pages 25 objects by 10 in ascending id order, for one type, relation and exact subject, and ends on a null cursor',\n\t\tasync run({ store }) {\n\t\t\tconst staff = entity('staff');\n\t\t\tconst ids = Array.from(\n\t\t\t\t{ length: 25 },\n\t\t\t\t(_, n) => `r${String(n).padStart(2, '0')}`,\n\t\t\t);\n\t\t\t// Written newest first, beside noise the answer must leave out.\n\t\t\tfor (const id of [...ids].reverse()) {\n\t\t\t\tawait store.write({\n\t\t\t\t\tadd: [\n\t\t\t\t\t\ttuple(entity('record', id), 'viewer', staff),\n\t\t\t\t\t\ttuple(entity('record', `${id}x`), 'editor', staff),\n\t\t\t\t\t\ttuple(entity('folder', id), 'viewer', staff),\n\t\t\t\t\t\ttuple(\n\t\t\t\t\t\t\tentity('record', `${id}y`),\n\t\t\t\t\t\t\t'viewer',\n\t\t\t\t\t\t\tset('staff', staff.id, 'x'),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst seen: string[] = [];\n\t\t\tconst sizes: number[] = [];\n\t\t\tlet after: string | null = null;\n\t\t\tfor (let page = 0; page < 10; page += 1) {\n\t\t\t\tconst answer = await store.findObjects({\n\t\t\t\t\ttype: 'record',\n\t\t\t\t\trelation: 'viewer',\n\t\t\t\t\tsubject: staff,\n\t\t\t\t\tafter,\n\t\t\t\t\tlimit: 10,\n\t\t\t\t});\n\t\t\t\tseen.push(...answer.items);\n\t\t\t\tsizes.push(answer.items.length);\n\t\t\t\tafter = answer.nextCursor;\n\t\t\t\tif (after === null) break;\n\t\t\t}\n\n\t\t\tequal(sizes, [10, 10, 5], 'findObjects: page sizes for 25 objects by 10');\n\t\t\tequal(seen, ids, 'findObjects: every id once, in ascending order');\n\t\t\tconst between = await store.findObjects({\n\t\t\t\ttype: 'record',\n\t\t\t\trelation: 'viewer',\n\t\t\t\tsubject: staff,\n\t\t\t\tafter: 'r04a',\n\t\t\t\tlimit: 2,\n\t\t\t});\n\t\t\tequal(\n\t\t\t\tbetween.items,\n\t\t\t\t['r05', 'r06'],\n\t\t\t\t'findObjects: a cursor naming no stored object should start after it',\n\t\t\t);\n\t\t},\n\t},\n\t{\n\t\tid: 'relations.deleteEntity',\n\t\tgroup,\n\t\tname: 'deletes every tuple naming an entity — as object, as subject, as a set’s entity — and counts them',\n\t\tasync run({ store }) {\n\t\t\tconst team = entity('team');\n\t\t\tconst other = entity('team');\n\t\t\tconst kept = [\n\t\t\t\ttuple(other, 'member', entity('staff')),\n\t\t\t\ttuple(entity('record'), 'viewer', set('team', other.id, 'member')),\n\t\t\t];\n\t\t\tawait store.write({\n\t\t\t\tadd: [\n\t\t\t\t\ttuple(team, 'member', entity('staff')),\n\t\t\t\t\ttuple(entity('record'), 'team', team),\n\t\t\t\t\ttuple(entity('record'), 'viewer', set('team', team.id, 'member')),\n\t\t\t\t\t...kept,\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tequal(\n\t\t\t\tawait store.deleteEntity(team),\n\t\t\t\t3,\n\t\t\t\t'deleteEntity: how many it deleted',\n\t\t\t);\n\t\t\tfor (const survivor of kept) {\n\t\t\t\tequal(\n\t\t\t\t\tawait store.has(survivor),\n\t\t\t\t\ttrue,\n\t\t\t\t\t'deleteEntity should not touch another entity',\n\t\t\t\t);\n\t\t\t}\n\t\t\tequal(\n\t\t\t\tawait store.deleteEntity(team),\n\t\t\t\t0,\n\t\t\t\t'deleteEntity replayed answers 0, not a failure',\n\t\t\t);\n\t\t},\n\t},\n];\n\n// ─── Outages ──────────────────────────────────────────────────────────────\n\n/**\n * For each method, a store that cannot answer must **reject** — never\n * `false`, `[]`, an empty page or `0`. A relation store that answers `false`\n * for an outage denies everybody everything and says nothing; one that\n * resolves a write it did not make leaves a revoked access standing.\n */\nfunction outage(\n\tmethod: RelationMethod,\n\tcall: (store: RelationStore) => Promise<unknown>,\n\tafter?: (store: RelationStore) => Promise<void>,\n): RelationCase {\n\treturn {\n\t\tid: `outage.${method}`,\n\t\tgroup: 'outage',\n\t\tname: `relations.${method} rejects when the store cannot answer — never false, [], an empty page or 0`,\n\t\tneeds: 'faults',\n\t\tasync run({ store, faults }) {\n\t\t\tawait store.write({ add: [seeded] });\n\t\t\tawait faults?.fail(method);\n\n\t\t\tconst error = await rejects(\n\t\t\t\tcall(store),\n\t\t\t\t`relations.${method} under an outage should reject`,\n\t\t\t);\n\t\t\tif (\n\t\t\t\terror instanceof JanusError ||\n\t\t\t\t(error as { name?: unknown })?.name === 'StoreFailure'\n\t\t\t) {\n\t\t\t\tisOurs(\n\t\t\t\t\terror,\n\t\t\t\t\tStoreFailure,\n\t\t\t\t\t'StoreFailure',\n\t\t\t\t\t`relations.${method} under an outage`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tok(\n\t\t\t\t(error as { code?: unknown } | null)?.code !== 'NOT_FOUND',\n\t\t\t\t`relations.${method} under an outage rejected with NOT_FOUND: an outage is never an absence`,\n\t\t\t);\n\t\t\tawait after?.(store);\n\t\t},\n\t};\n}\n\nconst seededObject: Entity = { type: 'record', id: 'seeded' };\nconst seededSubject: Entity = { type: 'staff', id: 'seeded' };\nconst seeded = tuple(seededObject, 'viewer', seededSubject);\n\nexport const relationOutageCases: readonly RelationCase[] = [\n\toutage(\n\t\t'write',\n\t\t(store) =>\n\t\t\tstore.write({\n\t\t\t\tremove: [seeded],\n\t\t\t\tadd: [tuple(seededObject, 'owner', seededSubject)],\n\t\t\t}),\n\t\t// A rejected write is one that did nothing: all of it, or none of it.\n\t\tasync (store) => {\n\t\t\tequal(\n\t\t\t\tawait store.has(seeded),\n\t\t\t\ttrue,\n\t\t\t\t'relations.write rejected, and still removed a tuple: a write is all or nothing',\n\t\t\t);\n\t\t\tequal(\n\t\t\t\tawait store.has(tuple(seededObject, 'owner', seededSubject)),\n\t\t\t\tfalse,\n\t\t\t\t'relations.write rejected, and still added a tuple: a write is all or nothing',\n\t\t\t);\n\t\t},\n\t),\n\toutage('has', (store) => store.has(seeded)),\n\toutage('findSubjectSets', (store) =>\n\t\tstore.findSubjectSets(seededObject, 'viewer'),\n\t),\n\toutage('findEntities', (store) => store.findEntities(seededObject, 'viewer')),\n\toutage('findObjects', (store) =>\n\t\tstore.findObjects({\n\t\t\ttype: 'record',\n\t\t\trelation: 'viewer',\n\t\t\tsubject: seededSubject,\n\t\t\tafter: null,\n\t\t\tlimit: 10,\n\t\t}),\n\t),\n\toutage('deleteEntity', (store) => store.deleteEntity(seededSubject)),\n];\n\nexport const allRelationCases: readonly RelationCase[] = [\n\t...relationStoreCases,\n\t...relationOutageCases,\n];\n\n// ─── Running them ─────────────────────────────────────────────────────────\n\n/** Runs one case against a freshly opened store, and closes it, pass or fail. */\nexport async function runRelationCase(\n\trelationCase: RelationCase,\n\tharness: RelationHarness,\n): Promise<{ readonly skipped: string } | { readonly passed: true }> {\n\tconst opened = await harness.open();\n\n\ttry {\n\t\tif (relationCase.needs === 'faults' && opened.faults === undefined) {\n\t\t\treturn { skipped: SKIP_REASONS.faults };\n\t\t}\n\t\tawait relationCase.run({\n\t\t\tstore: opened.store,\n\t\t\tfaults: opened.faults ?? null,\n\t\t});\n\t\treturn { passed: true };\n\t} finally {\n\t\tawait opened.close?.();\n\t}\n}\n\n/** Describes the whole relation suite under the adapter's test runner. */\nexport function describeRelationStores(options: {\n\treadonly name: string;\n\treadonly harness: RelationHarness;\n\treadonly runner?: ConformanceRunner;\n\t/** Case ids to skip, each with the reason — reported, never silent. */\n\treadonly skip?: Readonly<Record<string, string>>;\n\treadonly faults?: boolean;\n}): void {\n\tdescribeSuite({\n\t\ttitle: `${options.name} — @nxgt/janus relation conformance`,\n\t\tcases: allRelationCases,\n\t\trun: (relationCase) => runRelationCase(relationCase, options.harness),\n\t\trunner: options.runner ?? fromGlobals('describeRelationStores'),\n\t\tskip: options.skip ?? {},\n\t\tfaults: options.faults,\n\t});\n}\n\n/** The reference store, with faults: what the suite proves itself against. */\nexport function referenceRelationHarness(): RelationHarness {\n\treturn {\n\t\tasync open() {\n\t\t\tconst failing = new Set<string>();\n\t\t\tconst inner = createMemoryRelations();\n\t\t\tconst store = Object.fromEntries(\n\t\t\t\tObject.entries(inner).map(([method, fn]) => [\n\t\t\t\t\tmethod,\n\t\t\t\t\t(...args: unknown[]) =>\n\t\t\t\t\t\tfailing.has(method)\n\t\t\t\t\t\t\t? Promise.reject(\n\t\t\t\t\t\t\t\t\tnew StoreFailure(\n\t\t\t\t\t\t\t\t\t\t`relations.${method}: the store could not answer`,\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tslot: 'relations',\n\t\t\t\t\t\t\t\t\t\t\toperation: method,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: (fn as (...a: unknown[]) => unknown).apply(inner, args),\n\t\t\t\t]),\n\t\t\t) as unknown as RelationStore;\n\n\t\t\treturn {\n\t\t\t\tstore,\n\t\t\t\tfaults: {\n\t\t\t\t\tasync fail(method) {\n\t\t\t\t\t\tfailing.add(method);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n"
|
|
14
14
|
],
|
|
15
|
-
"mappings": "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",
|
|
16
|
-
"debugId": "
|
|
15
|
+
"mappings": "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",
|
|
16
|
+
"debugId": "7779A65B64EF7C9A64756E2164756E21",
|
|
17
17
|
"names": []
|
|
18
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../src/conformance/relations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAU/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,sCAAsC;AACtC,MAAM,MAAM,cAAc,GAAG,MAAM,aAAa,GAAG,MAAM,CAAC;AAE1D,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC/B,8DAA8D;IAC9D,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1B,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAwBD,eAAO,MAAM,kBAAkB,EAAE,SAAS,YAAY,EAiWrD,CAAC;
|
|
1
|
+
{"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../src/conformance/relations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAU/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,sCAAsC;AACtC,MAAM,MAAM,cAAc,GAAG,MAAM,aAAa,GAAG,MAAM,CAAC;AAE1D,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC/B,8DAA8D;IAC9D,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1B,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAwBD,eAAO,MAAM,kBAAkB,EAAE,SAAS,YAAY,EAiWrD,CAAC;AAoDF,eAAO,MAAM,mBAAmB,EAAE,SAAS,YAAY,EAqCtD,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,SAAS,YAAY,EAGnD,CAAC;AAIF,iFAAiF;AACjF,wBAAsB,eAAe,CACpC,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,eAAe,GACtB,OAAO,CAAC;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC,CAenE;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IACpC,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,IAAI,CASP;AAED,8EAA8E;AAC9E,wBAAgB,wBAAwB,IAAI,eAAe,CAiC1D"}
|
package/docs/troubleshooting.md
CHANGED
|
@@ -107,6 +107,7 @@ How the messages are shaped:
|
|
|
107
107
|
- [`describeJanusStores: no test runner on globalThis …`](#describejanusstores-no-test-runner-on-globalthis--pass-runner--describe-it--under-bun-test-import-them-from-buntest)
|
|
108
108
|
- [`JANUS_CONFORMANCE_SKIPPED` — `faults not provided: the outage invariant is not proven for this adapter`](#janus_conformance_skipped--faults-not-provided-the-outage-invariant-is-not-proven-for-this-adapter)
|
|
109
109
|
- [`the error is named <Class> but is not @nxgt/janus's <Class>: two copies of @nxgt/janus are installed …`](#the-error-is-named-class-but-is-not-nxgtjanuss-class-two-copies-of-nxgtjanus-are-installed-the-adapter-must-list-it-as-a-peer-dependency-never-a-dependency)
|
|
110
|
+
- [`expected <Class>, got <value>`](#expected-class-got-value)
|
|
110
111
|
- [`expected null, got undefined — an absence is null; undefined is a store that forgot to answer`](#expected-null-got-undefined--an-absence-is-null-undefined-is-a-store-that-forgot-to-answer)
|
|
111
112
|
|
|
112
113
|
---
|
|
@@ -1167,10 +1168,30 @@ A process warning, and the outage cases reported as skipped.
|
|
|
1167
1168
|
|
|
1168
1169
|
### `the error is named <Class> but is not @nxgt/janus's <Class>: two copies of @nxgt/janus are installed. The adapter must list it as a peer dependency, never a dependency`
|
|
1169
1170
|
|
|
1170
|
-
**When:** a conformance case that checks the class of an error — the outage and
|
|
1171
|
-
**Why:** the adapter imports its own copy of `@nxgt/janus`, so its `StoreFailure` or `
|
|
1171
|
+
**When:** a conformance case that checks the class of an error — the outage, conflict and not-found cases.
|
|
1172
|
+
**Why:** the adapter imports its own copy of `@nxgt/janus`, so its `StoreFailure`, `StoreConflict` or `NotFoundError` is not the core's, and `instanceof` fails.
|
|
1172
1173
|
**Fix:** move `@nxgt/janus` to `peerDependencies` (and `devDependencies` for the tests), then reinstall.
|
|
1173
1174
|
|
|
1175
|
+
### `expected <Class>, got <value>`
|
|
1176
|
+
|
|
1177
|
+
The case's own sentence first — `users.findUser under an outage`, `insertUser should refuse a taken login with StoreConflict`.
|
|
1178
|
+
|
|
1179
|
+
**When:** a conformance case that checks the class of an error, and the adapter threw — or answered — something else.
|
|
1180
|
+
**Why:** the port names the class: an outage is `StoreFailure`, a taken login `StoreConflict`, an update of an unknown id `NotFoundError`. A driver's own error, or an answer where a rejection was due, fails the case.
|
|
1181
|
+
**Fix:** wrap the driver's failure in the core's class, with the driver's error as `cause`:
|
|
1182
|
+
|
|
1183
|
+
```ts
|
|
1184
|
+
try {
|
|
1185
|
+
return await collection.findOne({ _id: id });
|
|
1186
|
+
} catch (cause) {
|
|
1187
|
+
throw new StoreFailure('users.findUser: the store could not answer', {
|
|
1188
|
+
slot: 'users',
|
|
1189
|
+
operation: 'findUser',
|
|
1190
|
+
cause,
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1174
1195
|
### `expected null, got undefined — an absence is null; undefined is a store that forgot to answer`
|
|
1175
1196
|
|
|
1176
1197
|
**When:** a conformance case on a `find*` method.
|