mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:58:17 +08:00
184 lines
5.8 KiB
JavaScript
184 lines
5.8 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
ClusterPromptOutputKeyRotationProcessConfigError,
|
|
runClusterPromptOutputKeyRotationProcess,
|
|
} = require('../dist/prompt-output/key-management/promptOutputKeyRotationProcess.js');
|
|
const {
|
|
pluginPackagePromptOutputKeyRotationMaterialProof,
|
|
} = require('@qinglong/ai/plugin-package-prompt-output-key-rotation');
|
|
|
|
function request() {
|
|
return {
|
|
rotationId: 'rotation-process-1',
|
|
requestId: 'rotation-process-request-1',
|
|
mutationId: 'rotation-process-mutation-1',
|
|
expectedSecretUid: 'rotation-secret-uid-1',
|
|
expectedActiveKeyId: 'key-before',
|
|
expectedCatalogDigest: '1'.repeat(64),
|
|
newKeyId: 'key-after',
|
|
};
|
|
}
|
|
|
|
function fakeDatabase(material) {
|
|
let preparation;
|
|
let completion;
|
|
let closed = 0;
|
|
const client = {
|
|
async query(sql, parameters = []) {
|
|
if (sql.includes('current_user AS "currentUser"')) {
|
|
return {
|
|
rows: [
|
|
{
|
|
currentUser: 'ql3_ai_maintenance',
|
|
maintenanceAuthority: true,
|
|
schemaAuthority: true,
|
|
artifactDeleteOnly: true,
|
|
tombstoneAppendOnly: true,
|
|
keyRetirementAppendOnly: true,
|
|
keyRotationAppendOnly: true,
|
|
terminalEvidenceReadOnly: true,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
if (sql.includes('key_rotation_preparations') && sql.includes('SELECT')) {
|
|
return {
|
|
rows: preparation
|
|
? [
|
|
{
|
|
rotationId: preparation.rotationId,
|
|
requestId: preparation.requestId,
|
|
mutationId: preparation.mutationId,
|
|
expectedSecretUid: preparation.expectedSecretUid,
|
|
expectedActiveKeyId: preparation.expectedActiveKeyId,
|
|
expectedCatalogDigest: preparation.expectedCatalogDigest,
|
|
newKeyId: preparation.newKeyId,
|
|
materialProof: preparation.materialProof,
|
|
preparedAtMs: String(preparation.preparedAtMs),
|
|
preparationDigest: preparation.preparationDigest,
|
|
preparationJson: preparation,
|
|
},
|
|
]
|
|
: [],
|
|
};
|
|
}
|
|
if (sql.includes('key_rotation_completions') && sql.includes('SELECT')) {
|
|
return {
|
|
rows: completion
|
|
? [
|
|
{
|
|
rotationId: completion.rotationId,
|
|
requestId: completion.requestId,
|
|
mutationId: completion.mutationId,
|
|
preparationDigest: completion.preparationDigest,
|
|
generation: String(completion.generation),
|
|
previousActiveKeyId: completion.previousActiveKeyId,
|
|
activeKeyId: completion.activeKeyId,
|
|
catalogDigest: completion.catalogDigest,
|
|
materialProof: completion.materialProof,
|
|
completedAtMs: String(completion.completedAtMs),
|
|
completionDigest: completion.completionDigest,
|
|
completionJson: completion,
|
|
},
|
|
]
|
|
: [],
|
|
};
|
|
}
|
|
if (
|
|
sql.includes('INSERT INTO') &&
|
|
sql.includes('key_rotation_preparations')
|
|
) {
|
|
preparation = JSON.parse(parameters[10]);
|
|
}
|
|
if (
|
|
sql.includes('INSERT INTO') &&
|
|
sql.includes('key_rotation_completions')
|
|
) {
|
|
completion = JSON.parse(parameters[11]);
|
|
}
|
|
return { rows: [], rowCount: 1 };
|
|
},
|
|
release() {},
|
|
};
|
|
const pool = {
|
|
async connect() {
|
|
return client;
|
|
},
|
|
async query(sql, parameters) {
|
|
return client.query(sql, parameters);
|
|
},
|
|
};
|
|
return {
|
|
database: {
|
|
pool,
|
|
async close() {
|
|
closed += 1;
|
|
},
|
|
},
|
|
get closed() {
|
|
return closed;
|
|
},
|
|
get completion() {
|
|
return completion;
|
|
},
|
|
materials: {
|
|
async rotate(command) {
|
|
return {
|
|
generation: 2,
|
|
previousActiveKeyId: command.expectedActiveKeyId,
|
|
activeKeyId: command.newKeyId,
|
|
catalogDigest: '2'.repeat(64),
|
|
materialProof: pluginPackagePromptOutputKeyRotationMaterialProof(
|
|
command.newKeyId,
|
|
material,
|
|
),
|
|
};
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
test('Cluster rotation process commits content-free prepare and completion', async () => {
|
|
const material = Buffer.alloc(32, 0x71);
|
|
const fixture = fakeDatabase(material);
|
|
const result = await runClusterPromptOutputKeyRotationProcess({
|
|
database: { connection: { connectionString: 'postgres://unused' } },
|
|
request: request(),
|
|
material,
|
|
materials: fixture.materials,
|
|
openDatabase: async () => fixture.database,
|
|
});
|
|
assert.equal(result.status, 'completed');
|
|
assert.equal(result.generation, 2);
|
|
assert.equal(result.activeKeyId, 'key-after');
|
|
assert.equal(result.readiness.keyRotationAppendOnly, true);
|
|
assert.equal(fixture.closed, 1);
|
|
const durable = JSON.stringify(fixture.completion);
|
|
assert.equal(durable.includes(material.toString('base64url')), false);
|
|
assert.equal(durable.includes(material.toString('hex')), false);
|
|
});
|
|
|
|
test('Cluster rotation process rejects invalid material before opening PostgreSQL', async () => {
|
|
let opened = false;
|
|
await assert.rejects(
|
|
runClusterPromptOutputKeyRotationProcess({
|
|
database: { connection: { connectionString: 'postgres://unused' } },
|
|
request: request(),
|
|
material: Buffer.alloc(31),
|
|
materials: {
|
|
async rotate() {
|
|
throw new Error('unreachable');
|
|
},
|
|
},
|
|
openDatabase: async () => {
|
|
opened = true;
|
|
throw new Error('must not open');
|
|
},
|
|
}),
|
|
ClusterPromptOutputKeyRotationProcessConfigError,
|
|
);
|
|
assert.equal(opened, false);
|
|
});
|