mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
+184
@@ -0,0 +1,184 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
|
||||
const {
|
||||
PostgresPluginPackagePromptOutputKeyRetirementRepository,
|
||||
assertPostgresPluginPackagePromptOutputKeyNotRetiring,
|
||||
} = require('../dist/prompt-output/storage/postgresPluginPackagePromptOutputKeyRetirementRepository.js');
|
||||
|
||||
function preparationRow(preparation) {
|
||||
return {
|
||||
keyId: preparation.keyId,
|
||||
retirementId: preparation.retirementId,
|
||||
requestId: preparation.requestId,
|
||||
mutationId: preparation.mutationId,
|
||||
catalogDigest: preparation.catalogDigest,
|
||||
materialProof: preparation.materialProof,
|
||||
preparedAtMs: String(preparation.preparedAtMs),
|
||||
preparationDigest: preparation.preparationDigest,
|
||||
preparationJson: preparation,
|
||||
};
|
||||
}
|
||||
|
||||
function completionRow(completion) {
|
||||
return {
|
||||
keyId: completion.keyId,
|
||||
retirementId: completion.retirementId,
|
||||
requestId: completion.requestId,
|
||||
mutationId: completion.mutationId,
|
||||
preparationDigest: completion.preparationDigest,
|
||||
retiredCatalogDigest: completion.retiredCatalogDigest,
|
||||
absenceProof: completion.absenceProof,
|
||||
completedAtMs: String(completion.completedAtMs),
|
||||
completionDigest: completion.completionDigest,
|
||||
completionJson: completion,
|
||||
};
|
||||
}
|
||||
|
||||
function storagePool(options = {}) {
|
||||
const queries = [];
|
||||
const preparations = new Map();
|
||||
const completions = new Map();
|
||||
const liveKeys = new Set(options.liveKeys ?? []);
|
||||
const client = {
|
||||
async query(sql, parameters = []) {
|
||||
queries.push({ sql, parameters });
|
||||
if (
|
||||
sql.includes('key_retirement_preparations') &&
|
||||
sql.includes('SELECT')
|
||||
) {
|
||||
const value = preparations.get(parameters[0]);
|
||||
return { rows: value ? [preparationRow(value)] : [] };
|
||||
}
|
||||
if (
|
||||
sql.includes('key_retirement_completions') &&
|
||||
sql.includes('SELECT')
|
||||
) {
|
||||
const value = completions.get(parameters[0]);
|
||||
return { rows: value ? [completionRow(value)] : [] };
|
||||
}
|
||||
if (sql.includes('count(*)::text')) {
|
||||
return { rows: [{ count: liveKeys.has(parameters[0]) ? '1' : '0' }] };
|
||||
}
|
||||
if (
|
||||
sql.includes('INSERT INTO') &&
|
||||
sql.includes('key_retirement_preparations')
|
||||
) {
|
||||
const value = JSON.parse(parameters[8]);
|
||||
preparations.set(value.keyId, value);
|
||||
return { rows: [], rowCount: 1 };
|
||||
}
|
||||
if (
|
||||
sql.includes('INSERT INTO') &&
|
||||
sql.includes('key_retirement_completions')
|
||||
) {
|
||||
const value = JSON.parse(parameters[9]);
|
||||
completions.set(value.keyId, value);
|
||||
return { rows: [], rowCount: 1 };
|
||||
}
|
||||
return { rows: [], rowCount: 0 };
|
||||
},
|
||||
release() {},
|
||||
};
|
||||
return {
|
||||
queries,
|
||||
preparations,
|
||||
completions,
|
||||
client,
|
||||
async connect() {
|
||||
return client;
|
||||
},
|
||||
async query(sql, parameters) {
|
||||
return client.query(sql, parameters);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function command() {
|
||||
return {
|
||||
keyId: 'cluster-key-retired',
|
||||
retirementId: 'retirement-a',
|
||||
requestId: 'request-a',
|
||||
mutationId: 'mutation-a',
|
||||
catalogDigest: '1'.repeat(64),
|
||||
materialProof: '2'.repeat(64),
|
||||
};
|
||||
}
|
||||
|
||||
test('PostgreSQL appends key retirement facts behind a shared key fence', async () => {
|
||||
const pool = storagePool();
|
||||
const times = [10_000, 20_000, 30_000, 40_000];
|
||||
const repository =
|
||||
new PostgresPluginPackagePromptOutputKeyRetirementRepository({
|
||||
pool,
|
||||
now: () => times.shift(),
|
||||
});
|
||||
|
||||
const prepared = await repository.prepare(command());
|
||||
assert.equal(prepared.status, 'created');
|
||||
assert.equal(prepared.preparation.preparedAtMs, 10_000);
|
||||
const replay = await repository.prepare(command());
|
||||
assert.equal(replay.status, 'existing');
|
||||
assert.deepEqual(replay.preparation, prepared.preparation);
|
||||
|
||||
const completed = await repository.complete({
|
||||
preparation: prepared.preparation,
|
||||
retiredCatalogDigest: '3'.repeat(64),
|
||||
absenceProof: '4'.repeat(64),
|
||||
});
|
||||
assert.equal(completed.status, 'created');
|
||||
assert.equal(completed.completion.completedAtMs, 30_000);
|
||||
const completionReplay = await repository.complete({
|
||||
preparation: prepared.preparation,
|
||||
retiredCatalogDigest: '3'.repeat(64),
|
||||
absenceProof: '4'.repeat(64),
|
||||
});
|
||||
assert.equal(completionReplay.status, 'existing');
|
||||
assert.deepEqual(completionReplay.completion, completed.completion);
|
||||
assert.deepEqual(await repository.find(command().keyId), {
|
||||
preparation: prepared.preparation,
|
||||
completion: completed.completion,
|
||||
});
|
||||
|
||||
const lockIndex = pool.queries.findIndex(({ sql }) =>
|
||||
sql.includes('pg_advisory_xact_lock'),
|
||||
);
|
||||
const countIndex = pool.queries.findIndex(({ sql }) =>
|
||||
sql.includes('count(*)::text'),
|
||||
);
|
||||
const insertIndex = pool.queries.findIndex(
|
||||
({ sql }) =>
|
||||
sql.includes('INSERT INTO') &&
|
||||
sql.includes('key_retirement_preparations'),
|
||||
);
|
||||
assert.equal(lockIndex >= 0, true);
|
||||
assert.equal(lockIndex < countIndex && countIndex < insertIndex, true);
|
||||
await assert.rejects(
|
||||
assertPostgresPluginPackagePromptOutputKeyNotRetiring(
|
||||
pool.client,
|
||||
command().keyId,
|
||||
),
|
||||
{ code: 'PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_CONFLICT' },
|
||||
);
|
||||
});
|
||||
|
||||
test('PostgreSQL refuses retirement while live ciphertext exists', async () => {
|
||||
const pool = storagePool({ liveKeys: [command().keyId] });
|
||||
const repository =
|
||||
new PostgresPluginPackagePromptOutputKeyRetirementRepository({
|
||||
pool,
|
||||
now: () => 10_000,
|
||||
});
|
||||
await assert.rejects(repository.prepare(command()), {
|
||||
code: 'PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_CONFLICT',
|
||||
});
|
||||
assert.equal(pool.preparations.size, 0);
|
||||
assert.equal(
|
||||
pool.queries.some(
|
||||
({ sql }) =>
|
||||
sql.includes('INSERT INTO') &&
|
||||
sql.includes('key_retirement_preparations'),
|
||||
),
|
||||
false,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user