forked from wangziqi/gongxue-base
244 lines
9.1 KiB
JavaScript
244 lines
9.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import {
|
|
APPLY_CONFIRMATION,
|
|
BOOTSTRAP_LOCK_KEY,
|
|
bootstrapPlatformAdmin,
|
|
buildConfig,
|
|
helpText,
|
|
publicResult,
|
|
sanitizeErrorMessage,
|
|
} from './bootstrap-platform-admin.js';
|
|
|
|
const AUTH_USER_ID = '11111111-1111-4111-8111-111111111111';
|
|
const PLATFORM_USER_ID = '22222222-2222-4222-8222-222222222222';
|
|
const LEGACY_USER_ID = '33333333-3333-4333-8333-333333333333';
|
|
|
|
function result(rows = []) {
|
|
return { rows, rowCount: rows.length };
|
|
}
|
|
|
|
function mockPool(responses) {
|
|
const queries = [];
|
|
const client = {
|
|
async query(sql, params = []) {
|
|
const normalized = String(sql).replace(/\s+/g, ' ').trim();
|
|
queries.push({ sql: normalized, params });
|
|
if (normalized === 'begin' || normalized === 'commit' || normalized === 'rollback') return result();
|
|
const response = responses.shift();
|
|
if (response instanceof Error) throw response;
|
|
if (!response) throw new Error(`Unexpected query: ${normalized}`);
|
|
return response;
|
|
},
|
|
release() {},
|
|
};
|
|
return {
|
|
queries,
|
|
pool: { async connect() { return client; } },
|
|
assertExhausted() { assert.equal(responses.length, 0, 'all expected database queries should run'); },
|
|
};
|
|
}
|
|
|
|
function config(overrides = {}) {
|
|
return {
|
|
databaseUrl: 'test-database-url',
|
|
authUserId: AUTH_USER_ID,
|
|
apply: false,
|
|
confirmation: APPLY_CONFIRMATION,
|
|
username: 'owner.admin',
|
|
email: 'owner@example.test',
|
|
phone: '13800001234',
|
|
name: 'Owner Admin',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
const parsed = buildConfig({
|
|
DATABASE_URL: 'test-database-url',
|
|
BOOTSTRAP_PLATFORM_ADMIN_AUTH_USER_ID: AUTH_USER_ID,
|
|
BOOTSTRAP_PLATFORM_ADMIN_USERNAME: 'owner.admin',
|
|
BOOTSTRAP_PLATFORM_ADMIN_NAME: 'Owner Admin',
|
|
}, []);
|
|
assert.equal(parsed.apply, false, 'bootstrap should default to dry-run');
|
|
assert.throws(
|
|
() => buildConfig({
|
|
DATABASE_URL: 'test-database-url',
|
|
BOOTSTRAP_PLATFORM_ADMIN_AUTH_USER_ID: AUTH_USER_ID,
|
|
}, ['--apply']),
|
|
new RegExp(APPLY_CONFIRMATION),
|
|
'apply must require the exact confirmation phrase',
|
|
);
|
|
assert.equal(
|
|
buildConfig({
|
|
DATABASE_URL: 'test-database-url',
|
|
BOOTSTRAP_PLATFORM_ADMIN_AUTH_USER_ID: AUTH_USER_ID,
|
|
}, ['--apply', '--confirm', APPLY_CONFIRMATION]).apply,
|
|
true,
|
|
);
|
|
await assert.rejects(
|
|
() => bootstrapPlatformAdmin(config({ apply: true, confirmation: '' }), { pool: mockPool([]).pool }),
|
|
new RegExp(APPLY_CONFIRMATION),
|
|
'direct callers must not bypass the apply confirmation gate',
|
|
);
|
|
assert.match(helpText(), /active, Auth-bound platform admin exists/);
|
|
assert.match(helpText(), /exactly one unbound legacy platform_admin/);
|
|
const sensitiveDatabaseUrl = ['postgresql:', '', 'bootstrap_user:bootstrap_password@db.internal:5432/tiku'].join('/');
|
|
const safeError = sanitizeErrorMessage(
|
|
new Error(`connection failed for ${sensitiveDatabaseUrl}`),
|
|
sensitiveDatabaseUrl,
|
|
);
|
|
assert.equal(safeError.includes(sensitiveDatabaseUrl), false, 'failure output must redact the configured database URL');
|
|
assert.equal(safeError.includes('bootstrap_password'), false, 'failure output must not expose database credentials');
|
|
|
|
{
|
|
const mock = mockPool([
|
|
result(),
|
|
result([{ exists: true }]),
|
|
result(),
|
|
result(),
|
|
result([{ id: LEGACY_USER_ID, username: 'legacy.admin', email: null, phone: null, name: 'Legacy Admin' }]),
|
|
]);
|
|
const dryRun = await bootstrapPlatformAdmin(config(), { pool: mock.pool });
|
|
mock.assertExhausted();
|
|
assert.equal(dryRun.dryRun, true);
|
|
assert.equal(dryRun.action, 'bind_legacy');
|
|
assert.equal(dryRun.platformUserId, LEGACY_USER_ID);
|
|
assert.equal(mock.queries[0].sql, 'begin');
|
|
assert.match(mock.queries[1].sql, /pg_advisory_xact_lock/);
|
|
assert.deepEqual(mock.queries[1].params, [BOOTSTRAP_LOCK_KEY]);
|
|
assert.equal(mock.queries.at(-1).sql, 'commit', 'dry-run should retain its transaction lock through normal commit');
|
|
assert.equal(mock.queries.some(query => query.sql === 'rollback'), false, 'successful dry-run should not break the transaction abstraction');
|
|
assert.equal(
|
|
mock.queries.some(query => /^(insert|update|delete)\b/i.test(query.sql)),
|
|
false,
|
|
'dry-run must execute no write statement',
|
|
);
|
|
assert.match(mock.queries[2].sql, /app\.auth_user_exists/);
|
|
assert.doesNotMatch(mock.queries[2].sql, /auth\.users/);
|
|
}
|
|
|
|
{
|
|
const saved = {
|
|
id: PLATFORM_USER_ID,
|
|
authUserId: AUTH_USER_ID,
|
|
username: 'owner.admin',
|
|
email: 'owner@example.test',
|
|
phone: '13800001234',
|
|
};
|
|
const mock = mockPool([
|
|
result(),
|
|
result([{ exists: true }]),
|
|
result(),
|
|
result(),
|
|
result(),
|
|
result([saved]),
|
|
result(),
|
|
]);
|
|
const applied = await bootstrapPlatformAdmin(config({ apply: true }), { pool: mock.pool });
|
|
mock.assertExhausted();
|
|
assert.equal(applied.action, 'create');
|
|
assert.equal(applied.auditAction, 'platform.admin.bootstrapped');
|
|
assert.equal(mock.queries.at(-1).sql, 'commit');
|
|
|
|
const userWrite = mock.queries.find(query => query.sql.includes('insert into public.platform_users'));
|
|
assert.ok(userWrite, 'apply should create the platform user inside the transaction');
|
|
assert.match(userWrite.sql, /platform_permissions/);
|
|
assert.match(userWrite.sql, /'\{"\*":true\}'::jsonb/);
|
|
|
|
const auditWrite = mock.queries.find(query => query.sql.includes('insert into public.audit_logs'));
|
|
assert.ok(auditWrite, 'apply should write an audit event in the same transaction');
|
|
assert.deepEqual(auditWrite.params, [PLATFORM_USER_ID, 'platform.admin.bootstrapped', 'create']);
|
|
assert.match(auditWrite.sql, /values \( null, null, \$2, 'platform_user', \$1::text/);
|
|
assert.match(auditWrite.sql, /'invokedBy', 'system_cli'/);
|
|
const auditPayload = JSON.stringify(auditWrite);
|
|
assert.equal(auditPayload.includes('owner@example.test'), false, 'audit must not contain email');
|
|
assert.equal(auditPayload.includes('13800001234'), false, 'audit must not contain phone');
|
|
assert.equal(auditPayload.includes(AUTH_USER_ID), false, 'audit must not contain the Supabase Auth user ID');
|
|
|
|
const output = JSON.stringify(publicResult(applied));
|
|
assert.equal(output.includes(AUTH_USER_ID), false, 'CLI output must mask Auth user ID');
|
|
assert.equal(output.includes(PLATFORM_USER_ID), false, 'CLI output must mask platform user ID');
|
|
assert.equal(output.includes('owner@example.test'), false, 'CLI output must mask email');
|
|
assert.equal(output.includes('13800001234'), false, 'CLI output must mask phone');
|
|
assert.equal(output.includes('owner.admin'), false, 'CLI output must mask username');
|
|
|
|
const emailUsernameOutput = JSON.stringify(publicResult({ ...applied, username: 'owner@example.test' }));
|
|
assert.equal(emailUsernameOutput.includes('owner@example.test'), false, 'email-shaped username must be masked');
|
|
const phoneUsernameOutput = JSON.stringify(publicResult({ ...applied, username: '13800001234' }));
|
|
assert.equal(phoneUsernameOutput.includes('13800001234'), false, 'phone-shaped username must be masked');
|
|
}
|
|
|
|
{
|
|
const mock = mockPool([
|
|
result(),
|
|
result([{ exists: true }]),
|
|
result([{ id: PLATFORM_USER_ID }]),
|
|
]);
|
|
await assert.rejects(
|
|
() => bootstrapPlatformAdmin(config({ apply: true }), { pool: mock.pool }),
|
|
/active, Auth-bound platform admin already exists/,
|
|
);
|
|
mock.assertExhausted();
|
|
assert.equal(mock.queries.at(-1).sql, 'rollback');
|
|
assert.equal(mock.queries.some(query => query.sql.startsWith('insert into')), false);
|
|
}
|
|
|
|
{
|
|
const mock = mockPool([
|
|
result(),
|
|
result([{ exists: true }]),
|
|
result(),
|
|
result(),
|
|
result([
|
|
{ id: LEGACY_USER_ID, username: 'legacy.one' },
|
|
{ id: PLATFORM_USER_ID, username: 'legacy.two' },
|
|
]),
|
|
]);
|
|
await assert.rejects(
|
|
() => bootstrapPlatformAdmin(config({ apply: true }), { pool: mock.pool }),
|
|
/Multiple unbound legacy platform admins/,
|
|
);
|
|
mock.assertExhausted();
|
|
assert.equal(mock.queries.at(-1).sql, 'rollback');
|
|
}
|
|
|
|
{
|
|
const mock = mockPool([
|
|
result(),
|
|
result([{ exists: true }]),
|
|
result(),
|
|
result(),
|
|
result([{ id: LEGACY_USER_ID, username: 'legacy.admin', email: null, phone: null, name: 'Legacy Admin' }]),
|
|
result([{
|
|
id: LEGACY_USER_ID,
|
|
authUserId: AUTH_USER_ID,
|
|
username: 'legacy.admin',
|
|
email: null,
|
|
phone: null,
|
|
}]),
|
|
result(),
|
|
]);
|
|
const applied = await bootstrapPlatformAdmin(config({ apply: true }), { pool: mock.pool });
|
|
mock.assertExhausted();
|
|
assert.equal(applied.action, 'bind_legacy');
|
|
const userWrite = mock.queries.find(query => query.sql.includes('insert into public.platform_users'));
|
|
assert.equal(userWrite.params[0], LEGACY_USER_ID, 'unique legacy row should be bound instead of creating a duplicate');
|
|
const auditWrite = mock.queries.find(query => query.sql.includes('insert into public.audit_logs'));
|
|
assert.deepEqual(auditWrite.params, [LEGACY_USER_ID, 'platform.admin.bootstrapped', 'bind_legacy']);
|
|
}
|
|
|
|
{
|
|
const mock = mockPool([
|
|
result(),
|
|
result([{ exists: false }]),
|
|
]);
|
|
await assert.rejects(
|
|
() => bootstrapPlatformAdmin(config({ apply: true }), { pool: mock.pool }),
|
|
/Supabase Auth user not found/,
|
|
);
|
|
mock.assertExhausted();
|
|
assert.equal(mock.queries.at(-1).sql, 'rollback');
|
|
assert.equal(mock.queries.some(query => /^(insert|update|delete)\b/i.test(query.sql)), false);
|
|
}
|
|
|
|
console.log('[PASS] first platform admin bootstrap safety contract');
|