mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
107 lines
3.9 KiB
JavaScript
107 lines
3.9 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { test } = require('node:test');
|
|
const {
|
|
LocalOwnerPepperCatalogFullError,
|
|
LocalOwnerPepperGenerationConflictError,
|
|
LocalOwnerPepperMutationConflictError,
|
|
} = require('@qinglong/runtime-core/local-owner-pepper');
|
|
const {
|
|
openLocalSqliteBootstrapDatabase,
|
|
} = require('../dist/storage/bootstrap');
|
|
const { migrateLocalSqlitePath } = require('../dist/migration/migration');
|
|
|
|
function fixture(t) {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-pepper-catalog-'));
|
|
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
|
return path.join(directory, 'qinglong3.sqlite');
|
|
}
|
|
|
|
function registration(index, overrides = {}) {
|
|
return {
|
|
mutationId: `018f4f58-7d5a-4d82-8f7d-5da12f05c0${String(index).padStart(2, '0')}`,
|
|
pepperKeyId: `owner-key-${index}`,
|
|
materialDigest: index.toString(16).padStart(64, '0'),
|
|
backupDigest: (index + 16).toString(16).padStart(64, '0'),
|
|
registeredAtMs: 100 + index,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('registers, activates and rotates with one append-only generation winner', async (t) => {
|
|
const databasePath = fixture(t);
|
|
const options = { databasePath, profile: 'edge' };
|
|
await migrateLocalSqlitePath(options);
|
|
const first = await openLocalSqliteBootstrapDatabase(options);
|
|
const second = await openLocalSqliteBootstrapDatabase(options);
|
|
t.after(() => Promise.all([first.close(), second.close()]));
|
|
|
|
const key1 = registration(1);
|
|
assert.equal((await first.ownerPepper.register(key1)).status, 'inserted');
|
|
assert.equal((await second.ownerPepper.register(key1)).status, 'existing');
|
|
const activation1 = {
|
|
mutationId: '018f4f58-7d5a-4d82-8f7d-5da12f05d001',
|
|
pepperKeyId: key1.pepperKeyId,
|
|
expectedGeneration: 0,
|
|
activatedAtMs: 200,
|
|
};
|
|
assert.equal(
|
|
(await first.ownerPepper.activate(activation1)).activation.generation,
|
|
1,
|
|
);
|
|
assert.equal(
|
|
(await second.ownerPepper.activate(activation1)).status,
|
|
'existing',
|
|
);
|
|
|
|
const key2 = registration(2);
|
|
const key3 = registration(3);
|
|
await first.ownerPepper.register(key2);
|
|
await first.ownerPepper.register(key3);
|
|
const contenders = [key2, key3].map((key, index) =>
|
|
[first, second][index].ownerPepper.activate({
|
|
mutationId: `018f4f58-7d5a-4d82-8f7d-5da12f05d00${index + 2}`,
|
|
pepperKeyId: key.pepperKeyId,
|
|
expectedGeneration: 1,
|
|
expectedActivePepperKeyId: key1.pepperKeyId,
|
|
activatedAtMs: 300 + index,
|
|
}),
|
|
);
|
|
const settled = await Promise.allSettled(contenders);
|
|
assert.equal(settled.filter(({ status }) => status === 'fulfilled').length, 1);
|
|
const rejection = settled.find(({ status }) => status === 'rejected');
|
|
assert.ok(rejection.reason instanceof LocalOwnerPepperGenerationConflictError);
|
|
|
|
const active = await first.ownerPepper.resolveActive();
|
|
assert.equal(active.generation, 2);
|
|
assert.equal((await first.ownerPepper.resolveKey(key1.pepperKeyId)).state, 'retired');
|
|
assert.equal(
|
|
(await first.ownerPepper.resolveKey(active.activePepperKeyId)).state,
|
|
'active',
|
|
);
|
|
});
|
|
|
|
test('rejects semantic mutation drift and caps the catalog at eight keys', async (t) => {
|
|
const databasePath = fixture(t);
|
|
const options = { databasePath, profile: 'standalone' };
|
|
await migrateLocalSqlitePath(options);
|
|
const database = await openLocalSqliteBootstrapDatabase(options);
|
|
t.after(() => database.close());
|
|
|
|
const first = registration(1);
|
|
await database.ownerPepper.register(first);
|
|
await assert.rejects(
|
|
database.ownerPepper.register({ ...first, backupDigest: 'f'.repeat(64) }),
|
|
LocalOwnerPepperMutationConflictError,
|
|
);
|
|
for (let index = 2; index <= 8; index += 1) {
|
|
await database.ownerPepper.register(registration(index));
|
|
}
|
|
await assert.rejects(
|
|
database.ownerPepper.register(registration(9)),
|
|
LocalOwnerPepperCatalogFullError,
|
|
);
|
|
});
|