mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:25:29 +08:00
191 lines
5.6 KiB
JavaScript
191 lines
5.6 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { createHash } = require('node:crypto');
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
ToolInvocationArtifactConflictError,
|
|
ToolInvocationArtifactUnavailableError,
|
|
createToolInvocationInputArtifact,
|
|
createToolInvocationPreviewArtifact,
|
|
} = require('@qinglong/runtime-core/tool-invocation-artifact');
|
|
const {
|
|
LocalSqliteOperationAuthority,
|
|
} = require('../dist/authority/operationAuthority');
|
|
const { migrateLocalSqliteDatabase } = require('../dist/migration/migration');
|
|
const {
|
|
LocalSqliteToolInvocationArtifactRepository,
|
|
} = require('../dist/tool-execution/toolInvocationArtifactRepository');
|
|
|
|
const KEY = Buffer.alloc(32, 7);
|
|
const NONCE = Buffer.alloc(12, 9);
|
|
const INVOCATION_ACTION_DIGEST = 'a'.repeat(64);
|
|
const ACTION_DIGEST = 'b'.repeat(64);
|
|
const REDACTION_DIGEST = 'c'.repeat(64);
|
|
|
|
function digest(value) {
|
|
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
}
|
|
|
|
function artifacts(index = 1, overrides = {}) {
|
|
const input = {
|
|
runId: `run-${String(index).padStart(3, '0')}`,
|
|
token: 'secret-value',
|
|
};
|
|
const common = {
|
|
projectId: 'project-001',
|
|
actionRef: `tool-plan:${input.runId}`,
|
|
sealedAtMs: 1_000 + index,
|
|
...overrides.common,
|
|
};
|
|
const inputArtifact = createToolInvocationInputArtifact(
|
|
{
|
|
artifactId: `artifact-input-${index}`,
|
|
requestedBy: { type: 'user', id: 'usr-owner' },
|
|
tool: { name: 'demo.compare', version: '1.0.0' },
|
|
input,
|
|
inputDigest: digest(input),
|
|
invocationActionDigest: INVOCATION_ACTION_DIGEST,
|
|
keyId: 'tool-key-test',
|
|
key: KEY,
|
|
...common,
|
|
...overrides.input,
|
|
},
|
|
() => NONCE,
|
|
);
|
|
const previewArtifact = createToolInvocationPreviewArtifact({
|
|
artifactId: `artifact-preview-${index}`,
|
|
actionDigest: ACTION_DIGEST,
|
|
redactionContractDigest: REDACTION_DIGEST,
|
|
preview: {
|
|
title: 'Compare Run',
|
|
summary: 'Reads one bounded Run projection',
|
|
fields: [
|
|
{ kind: 'identifier', label: 'Run', value: input.runId },
|
|
{ kind: 'redacted', label: 'Credential', value: null },
|
|
],
|
|
warnings: [],
|
|
},
|
|
...common,
|
|
...overrides.preview,
|
|
});
|
|
return { inputArtifact, previewArtifact };
|
|
}
|
|
|
|
async function harness() {
|
|
const client = new DatabaseSync(':memory:');
|
|
client.exec('PRAGMA foreign_keys = ON');
|
|
await migrateLocalSqliteDatabase(client);
|
|
client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3Projects" (
|
|
id, name, slug, status, version, created_at_ms, updated_at_ms
|
|
) VALUES ('project-001', 'Project', 'project-001', 'active', 1, 1, 1)`,
|
|
)
|
|
.run();
|
|
const authority = new LocalSqliteOperationAuthority(client);
|
|
return {
|
|
client,
|
|
repository: new LocalSqliteToolInvocationArtifactRepository(authority),
|
|
close: () => authority.close(),
|
|
};
|
|
}
|
|
|
|
test('atomically inserts and exactly replays one Artifact pair', async (t) => {
|
|
const current = await harness();
|
|
t.after(current.close);
|
|
const pair = artifacts();
|
|
assert.deepEqual(
|
|
await current.repository.put(pair.inputArtifact, pair.previewArtifact),
|
|
{ status: 'inserted' },
|
|
);
|
|
assert.deepEqual(
|
|
await current.repository.put(pair.inputArtifact, pair.previewArtifact),
|
|
{ status: 'existing' },
|
|
);
|
|
assert.deepEqual(
|
|
await current.repository.findInput(pair.inputArtifact.artifactId),
|
|
pair.inputArtifact,
|
|
);
|
|
assert.deepEqual(
|
|
await current.repository.findPreview(pair.previewArtifact.artifactId),
|
|
pair.previewArtifact,
|
|
);
|
|
assert.equal(
|
|
JSON.stringify(
|
|
current.client
|
|
.prepare(
|
|
`SELECT artifact_json FROM "ToolInvocationInputArtifacts"`,
|
|
)
|
|
.get(),
|
|
).includes('secret-value'),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('rejects pair drift and rolls back both rows', async (t) => {
|
|
const current = await harness();
|
|
t.after(current.close);
|
|
const pair = artifacts();
|
|
const detached = artifacts(2, {
|
|
common: {
|
|
projectId: 'project-001',
|
|
actionRef: 'tool-plan:detached',
|
|
sealedAtMs: 1_002,
|
|
},
|
|
});
|
|
await assert.rejects(
|
|
current.repository.put(pair.inputArtifact, detached.previewArtifact),
|
|
ToolInvocationArtifactConflictError,
|
|
);
|
|
assert.equal(
|
|
current.client
|
|
.prepare(
|
|
`SELECT count(*) AS count FROM "ToolInvocationInputArtifacts"`,
|
|
)
|
|
.get().count,
|
|
0,
|
|
);
|
|
assert.equal(
|
|
current.client
|
|
.prepare(
|
|
`SELECT count(*) AS count FROM "ToolInvocationPreviewArtifacts"`,
|
|
)
|
|
.get().count,
|
|
0,
|
|
);
|
|
});
|
|
|
|
test('fails closed when a stored projection is corrupted', async (t) => {
|
|
const current = await harness();
|
|
t.after(current.close);
|
|
const pair = artifacts();
|
|
await current.repository.put(pair.inputArtifact, pair.previewArtifact);
|
|
current.client.exec('PRAGMA ignore_check_constraints = ON');
|
|
current.client
|
|
.prepare(
|
|
`UPDATE "ToolInvocationInputArtifacts"
|
|
SET input_digest = ?
|
|
WHERE artifact_id = ?`,
|
|
)
|
|
.run('d'.repeat(64), pair.inputArtifact.artifactId);
|
|
current.client.exec('PRAGMA ignore_check_constraints = OFF');
|
|
await assert.rejects(
|
|
current.repository.findInput(pair.inputArtifact.artifactId),
|
|
ToolInvocationArtifactUnavailableError,
|
|
);
|
|
});
|
|
|
|
test('publishes only the explicit adapter subpath', () => {
|
|
const root = require('../dist');
|
|
const subpath = require('@qinglong/local-sqlite/tool-invocation-artifact');
|
|
assert.equal(
|
|
root.LocalSqliteToolInvocationArtifactRepository,
|
|
LocalSqliteToolInvocationArtifactRepository,
|
|
);
|
|
assert.equal(
|
|
subpath.LocalSqliteToolInvocationArtifactRepository,
|
|
LocalSqliteToolInvocationArtifactRepository,
|
|
);
|
|
});
|