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

229 lines
5.8 KiB
JavaScript

const assert = require('node:assert/strict');
const test = require('node:test');
const {
createPluginPackagePromptOutputArtifact,
} = require('../dist/prompt-output/pluginPackagePromptOutputArtifact.js');
const {
InvalidPluginPackagePromptOutputReadError,
PluginPackagePromptOutputReadService,
PluginPackagePromptOutputReadUnavailableError,
} = require('../dist/prompt-output/pluginPackagePromptOutputRead.js');
const NOW = 1_700_000_000_000;
const KEY = Buffer.alloc(32, 7);
const PRINCIPAL = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'user-a' }),
authenticationId: 'auth-a',
authenticatedAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
assurance: 'multi_factor',
});
const RESULT = Object.freeze({
provider: 'openai-compatible',
model: 'bounded-model',
text: 'private durable output',
finishReason: 'stop',
usage: Object.freeze({
inputTokens: 5,
outputTokens: 3,
totalTokens: 8,
costMicros: 11,
}),
});
function artifact() {
return createPluginPackagePromptOutputArtifact(
{
projectId: 'project-a',
runId: 'run-a',
stepRunId: 'step-a',
invocationId: 'invocation-a',
requestedBy: PRINCIPAL.subject,
result: RESULT,
retentionPolicy: { revision: 'retention-v1', retentionMs: 86_400_000 },
keyId: 'prompt-key-1',
key: Buffer.from(KEY),
sealedAtMs: NOW - 1_000,
},
() => Buffer.alloc(12, 9),
);
}
function harness(overrides = {}) {
const stored = artifact();
const calls = [];
let resolvedKey;
const service = new PluginPackagePromptOutputReadService({
artifacts: {
async find(id) {
calls.push(`find:${id}`);
return stored;
},
async put() {
throw new Error('unreachable');
},
},
authorizer: {
async authorize(request) {
calls.push(`authorize:${request.artifactId}`);
return { effect: 'allow' };
},
},
retention: {
async inspect(request) {
calls.push(`retention:${request.reference.artifactId}`);
return { state: 'retained' };
},
},
keys: {
async active() {
throw new Error('read must not request active key');
},
async resolve(keyId) {
calls.push(`key:${keyId}`);
resolvedKey = Buffer.from(KEY);
return { keyId, key: resolvedKey };
},
},
now: () => NOW,
...overrides,
});
return { service, stored, calls, resolvedKey: () => resolvedKey };
}
function command(stored, overrides = {}) {
return {
principal: PRINCIPAL,
projectId: stored.projectId,
runId: stored.runId,
artifactId: stored.artifactId,
artifactDigest: stored.artifactDigest,
...overrides,
};
}
test('reads only after metadata, policy and retention and wipes resolved key', async () => {
const state = harness();
const result = await state.service.read(command(state.stored));
assert.equal(result.status, 'available');
assert.deepEqual(result.result, RESULT);
assert.equal(result.reference.artifactDigest, state.stored.artifactDigest);
assert.deepEqual(state.calls, [
`find:${state.stored.artifactId}`,
`authorize:${state.stored.artifactId}`,
`retention:${state.stored.artifactId}`,
'key:prompt-key-1',
]);
assert.equal(
state.resolvedKey().every((byte) => byte === 0),
true,
);
});
test('masks absent and identity-drifted Artifacts before policy or key access', async () => {
const missingCalls = [];
const missing = harness({
artifacts: {
async find() {
missingCalls.push('find');
return null;
},
async put() {
throw new Error('unreachable');
},
},
});
assert.equal(
(await missing.service.read(command(missing.stored))).status,
'not_found',
);
assert.deepEqual(missingCalls, ['find']);
const drift = harness();
assert.equal(
(
await drift.service.read(
command(drift.stored, { projectId: 'project-b' }),
)
).status,
'not_found',
);
assert.deepEqual(drift.calls, [`find:${drift.stored.artifactId}`]);
});
test('masks policy denial and tombstone without resolving key material', async () => {
const denied = harness({
authorizer: {
async authorize() {
return { effect: 'deny', reasonCode: 'artifact_read_denied' };
},
},
});
assert.equal(
(await denied.service.read(command(denied.stored))).status,
'not_found',
);
assert.equal(
denied.calls.some((call) => call.startsWith('key:')),
false,
);
const tombstoned = harness({
retention: {
async inspect() {
return {
state: 'tombstoned',
tombstonedAtMs: NOW - 1,
tombstoneDigest: 'a'.repeat(64),
};
},
},
});
assert.equal(
(await tombstoned.service.read(command(tombstoned.stored))).status,
'not_found',
);
assert.equal(
tombstoned.calls.some((call) => call.startsWith('key:')),
false,
);
});
test('fails closed for invalid requests, corrupt decisions and missing keys', async () => {
const invalid = harness();
await assert.rejects(
invalid.service.read(command(invalid.stored, { artifactDigest: 'bad' })),
InvalidPluginPackagePromptOutputReadError,
);
assert.deepEqual(invalid.calls, []);
const corruptDecision = harness({
authorizer: {
async authorize() {
return { effect: 'allow', widened: true };
},
},
});
await assert.rejects(
corruptDecision.service.read(command(corruptDecision.stored)),
PluginPackagePromptOutputReadUnavailableError,
);
const missingKey = harness({
keys: {
async active() {
throw new Error('unreachable');
},
async resolve() {
return null;
},
},
});
await assert.rejects(
missingKey.service.read(command(missingKey.stored)),
PluginPackagePromptOutputReadUnavailableError,
);
});