Files
LWLT-AIBOT/control-plane/test/input-attachment.test.ts
2026-08-31 15:42:11 +08:00

149 lines
5.8 KiB
TypeScript

import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import { sha256Bytes } from '../src/crypto.js';
import {
InputAttachmentError,
createPinnedAttachmentLookup,
decodeInlineInputAttachment,
downloadAgentBusInputAttachment,
parseAgentBusInputAttachment,
resolveAgentBusAttachmentAddresses,
validateAgentBusAttachmentUrl
} from '../src/input-attachment.js';
function codeOf(callback: () => unknown): string {
try {
callback();
return '';
} catch (error) {
return error instanceof InputAttachmentError ? error.code : String(error);
}
}
test('manual roster attachment base64 is bounded and hash verified', () => {
const content = Buffer.from('PK\u0003\u0004synthetic-workbook');
const decoded = decodeInlineInputAttachment({
name: 'synthetic.xlsx',
content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
size: content.byteLength,
sha256: sha256Bytes(content),
content_base64: content.toString('base64')
}, 1_000);
assert.equal(decoded.fileName, 'synthetic.xlsx');
assert.deepEqual(decoded.content, content);
assert.equal(decoded.source, 'manual');
});
test('manual roster attachment rejects unsupported names, invalid base64 and mismatched hash', () => {
assert.equal(codeOf(() => decodeInlineInputAttachment({
name: 'synthetic.csv', content_base64: 'YQ=='
}, 1_000)), 'roster_file_type_unsupported');
assert.equal(codeOf(() => decodeInlineInputAttachment({
name: 'synthetic.xls', content_base64: '%%%'
}, 1_000)), 'roster_file_base64_invalid');
assert.equal(codeOf(() => decodeInlineInputAttachment({
name: 'synthetic.xls', sha256: 'a'.repeat(64), content_base64: 'YQ=='
}, 1_000)), 'roster_file_sha256_mismatch');
});
test('AgentBus attachment URLs allow private HTTPS endpoints but reject unsafe URL syntax', () => {
for (const value of [
'http://files.example.test/list.xlsx',
'ftp://files.example.test/list.xlsx',
'https://user:secret@files.example.test/list.xlsx'
]) {
assert.equal(codeOf(() => validateAgentBusAttachmentUrl(value)), 'roster_attachment_url_unsafe');
}
for (const value of [
'https://files.example.com/list.xlsx',
'https://localhost/list.xlsx',
'https://127.0.0.1/list.xlsx',
'https://10.0.0.8/list.xlsx',
'https://[::1]/list.xlsx'
]) {
assert.equal(validateAgentBusAttachmentUrl(value).protocol, 'https:', value);
}
});
test('AgentBus attachment metadata is normalized without exposing URL credentials', () => {
const reference = parseAgentBusInputAttachment({
name: 'synthetic.xls',
content_type: 'application/vnd.ms-excel',
size: 128,
sha256: 'a'.repeat(64),
url: 'https://files.example.com/roster.xls'
}, 1_000);
assert.equal(reference.name, 'synthetic.xls');
assert.equal(reference.size, 128);
assert.equal(reference.sha256, 'a'.repeat(64));
});
test('AgentBus attachment DNS accepts internal names and private literal addresses', async () => {
assert.deepEqual(await resolveAgentBusAttachmentAddresses('10.0.0.8'), [
{ address: '10.0.0.8', family: 4 }
]);
assert.deepEqual(await resolveAgentBusAttachmentAddresses('fd00::8'), [
{ address: 'fd00::8', family: 6 }
]);
const localhost = await resolveAgentBusAttachmentAddresses('localhost');
assert.ok(localhost.length > 0);
assert.ok(localhost.every((entry) => entry.family === 4 || entry.family === 6));
});
test('AgentBus attachment DNS pinning follows the Node lookup callback shape', async () => {
const address = { address: '10.0.0.8', family: 4 };
const lookup = createPinnedAttachmentLookup(address);
await new Promise<void>((resolve, reject) => {
lookup('internal.example', { all: true }, (error, addresses, family) => {
try {
assert.ifError(error);
assert.deepEqual(addresses, [address]);
assert.equal(family, undefined);
resolve();
} catch (assertionError) {
reject(assertionError);
}
});
});
await new Promise<void>((resolve, reject) => {
lookup('internal.example', { all: false }, (error, resolvedAddress, family) => {
try {
assert.ifError(error);
assert.equal(resolvedAddress, address.address);
assert.equal(family, address.family);
resolve();
} catch (assertionError) {
reject(assertionError);
}
});
});
});
test('AgentBus attachment diagnostics expose stages and codes without URL data', async () => {
const events: Array<{ event: string; metadata: Record<string, unknown> }> = [];
await assert.rejects(
() => downloadAgentBusInputAttachment({
name: 'synthetic.xls',
contentType: 'application/vnd.ms-excel',
size: 128,
url: 'https://user:secret@127.0.0.1/private-roster.xls?token=secret'
}, 1_000, (event, metadata) => events.push({ event, metadata })),
(error: unknown) => error instanceof InputAttachmentError
&& error.code === 'roster_attachment_url_unsafe'
);
assert.deepEqual(events.map((event) => event.event), ['download_started', 'download_failed']);
assert.equal(events[1].metadata.error_code, 'roster_attachment_url_unsafe');
assert.doesNotMatch(JSON.stringify(events), /127\.0\.0\.1|private-roster|token|secret/u);
});
test('input attachment migration stores only encrypted normalized data and waiting-task index', async () => {
const sql = await readFile(new URL('../migrations/014_task_input_attachments.sql', import.meta.url), 'utf8');
assert.match(sql, /CREATE TABLE IF NOT EXISTS task_input_attachments/);
assert.match(sql, /normalized_text_ciphertext text/);
assert.match(sql, /file_name_ciphertext text NOT NULL/);
assert.doesNotMatch(sql, /\n\s*file_name text/);
assert.doesNotMatch(sql, /public_url|content bytea|content_ciphertext/);
assert.match(sql, /WHERE status = 'awaiting_attachment'/);
});