Files
qinglong/packages/ql3-ai/test/localModelPriceCatalogManagementRepository.test.cjs
T

339 lines
10 KiB
JavaScript

const assert = require('node:assert/strict');
const test = require('node:test');
const { DatabaseSync } = require('node:sqlite');
const {
ModelPriceCatalogConflictError,
createModelPriceCatalogPublishCommand,
createModelPriceCatalogTransitionCommand,
} = require('../dist/pricing/modelPriceCatalog.js');
const {
ModelPriceCatalogManagementSeparationOfDutyError,
createModelPriceCatalogAuthorizationCommand,
createModelPriceCatalogManagementService,
createModelPriceCatalogPolicyDecision,
} = require('../dist/pricing/modelPriceCatalogManagement.js');
const {
LocalModelPriceCatalogRepository,
} = require('../dist/pricing/storage/localModelPriceCatalogRepository.js');
const {
migrateLocalModelInvocationFeature,
} = require('@qinglong/ai/model-invocation-migration');
function createMainSqliteContract(client) {
client.exec(`
CREATE TABLE "QingLong3SchemaMigrations" (
migration_id TEXT PRIMARY KEY,
stream_id TEXT NOT NULL,
dialect TEXT NOT NULL,
checksum TEXT NOT NULL,
applied_at_ms INTEGER NOT NULL
);
CREATE TABLE "Runs" (id TEXT PRIMARY KEY);
CREATE TABLE "RunEvents" (id TEXT PRIMARY KEY);
CREATE TABLE "StepRuns" (
id TEXT PRIMARY KEY,
run_id TEXT NOT NULL,
UNIQUE (run_id, id)
);
CREATE TABLE "StepRunMutations" (mutation_id TEXT PRIMARY KEY);
`);
}
async function fixture(decisionMode = 'human_confirmation') {
const client = new DatabaseSync(':memory:');
client.exec('PRAGMA foreign_keys = ON');
createMainSqliteContract(client);
await migrateLocalModelInvocationFeature(client);
const repository = new LocalModelPriceCatalogRepository(client);
const now = Date.now();
const service = createModelPriceCatalogManagementService(repository, {
decisionMode,
authorizer: {
async authorize() {
return createModelPriceCatalogPolicyDecision({
effect: 'allow',
revision: 'platform-policy-1',
reasons: ['catalog_operator'],
});
},
},
now: () => now,
});
return { client, repository, service, now };
}
function principal(userId, now) {
return {
subject: { type: 'user', id: userId },
authenticationId: `auth-${userId}`,
authenticatedAtMs: now - 1_000,
expiresAtMs: now + 60_000,
assurance: 'multi_factor',
};
}
function publication(userId, now, overrides = {}) {
return {
authorizationId: 'authorize-publish-1',
requestId: 'request-publish-1',
mutationId: 'publish-price-1',
provider: 'remote',
model: 'model-a',
principal: principal(userId, now),
priceRevision: 'price-1',
currency: 'USD',
inputMicrosPerMillionTokens: 150_000,
outputMicrosPerMillionTokens: 600_000,
...overrides,
};
}
function activation(userId, now, overrides = {}) {
return {
authorizationId: 'authorize-activate-1',
requestId: 'request-activate-1',
mutationId: 'activate-price-1',
provider: 'remote',
model: 'model-a',
principal: principal(userId, now),
expectedGeneration: 0,
expectedHeadDigest: null,
action: 'activate',
priceRevision: 'price-1',
...overrides,
};
}
test('SQLite atomically commits catalog mutations with exact authorization facts', async () => {
const { client, repository, service, now } = await fixture();
const published = await service.publish(publication('owner', now));
const replayed = await service.publish(publication('owner', now));
assert.equal(published.status, 'created');
assert.equal(replayed.status, 'existing');
assert.deepEqual(replayed, {
status: 'existing',
publication: published.publication,
authorization: published.authorization,
});
const activated = await service.transition(activation('owner', now));
assert.equal(activated.status, 'created');
assert.equal(activated.head.activePriceRevision, 'price-1');
assert.deepEqual(
await repository.findAuthorization('authorize-activate-1'),
activated.authorization,
);
assert.deepEqual(
{
...client
.prepare(
`SELECT
(SELECT count(*) FROM "ModelPriceCatalogPublications") AS publications,
(SELECT count(*) FROM "ModelPriceCatalogHeads") AS heads,
(SELECT count(*) FROM "ModelPriceCatalogAuthorizations") AS authorizations`,
)
.get(),
},
{ publications: 1, heads: 1, authorizations: 2 },
);
assert.equal(client.prepare('PRAGMA foreign_key_check').all().length, 0);
await assert.rejects(
service.publish(
publication('owner', now, {
authorizationId: 'authorize-publish-drift',
}),
),
ModelPriceCatalogConflictError,
);
assert.equal(
client
.prepare(
`SELECT count(*) AS count FROM "ModelPriceCatalogAuthorizations"`,
)
.get().count,
2,
);
client.close();
});
test('SQLite replay accepts fresh reauthentication but preserves the first authorization fact', async () => {
const { client, repository, service, now } = await fixture();
const created = await service.publish(publication('owner', now));
const freshService = createModelPriceCatalogManagementService(repository, {
decisionMode: 'human_confirmation',
authorizer: {
async authorize() {
return createModelPriceCatalogPolicyDecision({
effect: 'allow',
revision: 'platform-policy-1',
reasons: ['catalog_operator'],
});
},
},
now: () => now + 1_000,
});
const replayed = await freshService.publish(
publication('owner', now + 1_000),
);
assert.equal(replayed.status, 'existing');
assert.deepEqual(replayed.authorization, created.authorization);
await assert.rejects(
freshService.publish(
publication('owner', now + 1_000, {
principal: {
...principal('owner', now + 1_000),
authenticationId: 'auth-different-proof',
},
}),
),
ModelPriceCatalogConflictError,
);
client.close();
});
test('SQLite authorization fence hook runs inside the catalog transaction before mutation', async () => {
const client = new DatabaseSync(':memory:');
client.exec('PRAGMA foreign_keys = ON');
createMainSqliteContract(client);
await migrateLocalModelInvocationFeature(client);
let checked = 0;
const repository = new LocalModelPriceCatalogRepository(client, {
beforeAuthorizedMutation() {
checked += 1;
const error = new Error('credential fence rejected');
error.code = 'TEST_CREDENTIAL_FENCE_REJECTED';
throw error;
},
});
const now = Date.now();
const service = createModelPriceCatalogManagementService(repository, {
decisionMode: 'human_confirmation',
authorizer: {
async authorize() {
return createModelPriceCatalogPolicyDecision({
effect: 'allow',
revision: 'platform-policy-1',
reasons: ['catalog_operator'],
});
},
},
now: () => now,
});
await assert.rejects(service.publish(publication('owner', now)), {
code: 'MODEL_PRICE_CATALOG_UNAVAILABLE',
});
assert.equal(checked, 1);
assert.deepEqual(
{
...client
.prepare(
`SELECT
(SELECT count(*) FROM "ModelPriceCatalogPublications") AS publications,
(SELECT count(*) FROM "ModelPriceCatalogAuthorizations") AS authorizations`,
)
.get(),
},
{ publications: 0, authorizations: 0 },
);
assert.equal(client.isTransaction, false);
client.close();
});
test('authorized activation rejects legacy raw publication without evidence', async () => {
const { client, repository, service, now } = await fixture();
await repository.publish(
createModelPriceCatalogPublishCommand({
provider: 'remote',
model: 'model-a',
priceRevision: 'price-1',
currency: 'USD',
inputMicrosPerMillionTokens: 150_000,
outputMicrosPerMillionTokens: 600_000,
mutationId: 'legacy-publish-price-1',
publishedByUserId: 'owner',
}),
);
await assert.rejects(
service.transition(activation('owner', now)),
ModelPriceCatalogConflictError,
);
assert.equal(
client
.prepare(`SELECT count(*) AS count FROM "ModelPriceCatalogHeads"`)
.get().count,
0,
);
assert.equal(
client
.prepare(
`SELECT count(*) AS count FROM "ModelPriceCatalogAuthorizations"`,
)
.get().count,
0,
);
client.close();
});
test('SQLite enforces separation of duty again inside the catalog transaction', async () => {
const { client, repository, service, now } = await fixture(
'separation_of_duty',
);
await service.publish(publication('publisher', now));
await assert.rejects(
service.transition(activation('publisher', now)),
ModelPriceCatalogManagementSeparationOfDutyError,
);
const directCommand = createModelPriceCatalogTransitionCommand({
provider: 'remote',
model: 'model-a',
expectedGeneration: 0,
expectedHeadDigest: null,
action: 'activate',
priceRevision: 'price-1',
mutationId: 'direct-activate-price-1',
changedByUserId: 'publisher',
});
const directAuthorization = createModelPriceCatalogAuthorizationCommand({
authorizationId: 'direct-same-user',
requestId: 'direct-same-user-request',
operation: 'activate',
provider: 'remote',
model: 'model-a',
priceRevision: 'price-1',
catalogCommandDigest: directCommand.commandDigest,
principal: principal('publisher', now),
policy: createModelPriceCatalogPolicyDecision({
effect: 'allow',
revision: 'platform-policy-1',
reasons: ['catalog_operator'],
}),
decisionMode: 'separation_of_duty',
});
await assert.rejects(
repository.transitionAuthorized(directCommand, directAuthorization),
ModelPriceCatalogConflictError,
);
const reviewed = await service.transition(activation('reviewer', now));
assert.equal(reviewed.head.activePriceRevision, 'price-1');
assert.equal(
client
.prepare(`SELECT count(*) AS count FROM "ModelPriceCatalogHeads"`)
.get().count,
1,
);
assert.equal(
await repository
.findCurrent('remote', 'model-a')
.then((head) => head.activePriceRevision),
'price-1',
);
client.close();
});