phase8-p1b-store-persistence.test.mjs
81 lines 2.9 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Phase 8 P1b — data-integrity tier: store persistence across restart (§9).
3 */
4
5 import { describe, test, before, after } from 'node:test';
6 import assert from 'node:assert/strict';
7 import fs from 'fs';
8 import path from 'path';
9 import {
10 makeTempDataDir,
11 bootstrapTestAdmin,
12 TEST_PASSPHRASE,
13 } from '../helpers/phase8-p1b-local-auth-harness.mjs';
14 import {
15 loadCredentialStore,
16 loadLoginAttempts,
17 saveLoginAttempts,
18 recordLoginFailure,
19 credentialsPath,
20 } from '../../hub/lib/local-auth.mjs';
21 import {
22 generateSetupToken,
23 consumeSetupToken,
24 bootstrapPath,
25 loadBootstrapRecord,
26 } from '../../hub/lib/local-auth-bootstrap.mjs';
27 import { readRolesObject, writeRolesFile } from '../../hub/roles.mjs';
28
29 describe('phase8-p1b store persistence (data-integrity)', () => {
30 /** @type {{ dataDir: string, cleanup: () => void }} */
31 let tmp;
32
33 before(async () => {
34 tmp = await makeTempDataDir();
35 await bootstrapTestAdmin(tmp.dataDir);
36 });
37
38 after(() => tmp.cleanup());
39
40 test('credential store survives reload', () => {
41 const before = loadCredentialStore(tmp.dataDir);
42 const reloaded = loadCredentialStore(tmp.dataDir);
43 assert.equal(Object.keys(reloaded.credentials).length, Object.keys(before.credentials).length);
44 assert.ok(fs.existsSync(credentialsPath(tmp.dataDir)));
45 });
46
47 test('bootstrap record unlinked after consume', async () => {
48 const dir = (await makeTempDataDir()).dataDir;
49 const { token } = generateSetupToken(dir, 'admin', '15m');
50 assert.ok(loadBootstrapRecord(dir));
51 await consumeSetupToken(dir, token, 'admin', TEST_PASSPHRASE, { ip: '127.0.0.1' });
52 assert.equal(fs.existsSync(bootstrapPath(dir)), false);
53 fs.rmSync(dir, { recursive: true, force: true });
54 });
55
56 test('login-attempts file authoritative across reload', () => {
57 const attempts = loadLoginAttempts(tmp.dataDir);
58 recordLoginFailure(attempts, 'local:admin_001');
59 saveLoginAttempts(tmp.dataDir, attempts);
60 const reloaded = loadLoginAttempts(tmp.dataDir);
61 assert.equal(reloaded['local:admin_001'].failures, 1);
62 });
63
64 test('role + credential stores stay consistent on role change', () => {
65 const roles = readRolesObject(tmp.dataDir);
66 roles['local:admin_001'] = 'editor';
67 writeRolesFile(tmp.dataDir, roles);
68 const store = loadCredentialStore(tmp.dataDir);
69 assert.ok(store.credentials['local:admin_001']);
70 assert.equal(readRolesObject(tmp.dataDir)['local:admin_001'], 'editor');
71 });
72
73 test('mustRotatePassphrase cleared only after rotation success path', async () => {
74 const dir = (await makeTempDataDir()).dataDir;
75 const { token } = generateSetupToken(dir, 'admin', '15m');
76 await consumeSetupToken(dir, token, 'admin', TEST_PASSPHRASE, { ip: '127.0.0.1' });
77 const store = loadCredentialStore(dir);
78 assert.equal(store.credentials['local:admin_001'].mustRotatePassphrase, true);
79 fs.rmSync(dir, { recursive: true, force: true });
80 });
81 });
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago