Files
LWLT-AIBOT/control-plane/test/input-attachment.test.ts

92 lines
3.7 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,
decodeInlineInputAttachment,
isPrivateOrReservedIp,
parseAgentBusInputAttachment,
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 require public HTTPS endpoints', () => {
for (const value of [
'http://files.example.test/list.xlsx',
'https://localhost/list.xlsx',
'https://127.0.0.1/list.xlsx',
'https://10.0.0.8/list.xlsx',
'https://[::1]/list.xlsx',
'https://user:secret@files.example.test/list.xlsx'
]) {
assert.equal(codeOf(() => validateAgentBusAttachmentUrl(value)), 'roster_attachment_url_unsafe');
}
assert.equal(validateAgentBusAttachmentUrl('https://files.example.com/list.xlsx').protocol, 'https:');
});
test('private and documentation address ranges are rejected', () => {
for (const address of ['0.0.0.1', '10.1.2.3', '100.64.0.1', '127.0.0.1', '169.254.1.1', '172.31.0.1', '192.168.1.1', '198.51.100.1', '203.0.113.8', '::1', 'fc00::1', 'fe80::1', '2001:db8::1']) {
assert.equal(isPrivateOrReservedIp(address), true, address);
}
assert.equal(isPrivateOrReservedIp('8.8.8.8'), false);
assert.equal(isPrivateOrReservedIp('2606:4700:4700::1111'), false);
});
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('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'/);
});