chore: initial commit

This commit is contained in:
2026-10-22 17:07:08 +08:00
commit 0acef64f48
200 changed files with 35546 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {mkdtemp, mkdir, writeFile, appendFile, rename, symlink, rm} from 'node:fs/promises';
import {tmpdir} from 'node:os';
import path from 'node:path';
import {tsImport} from 'tsx/esm/api';
const {BotFileService} = await tsImport('../bot/forward/files.ts', import.meta.url);
const deadline = () => Date.now() + 20_000;
const code = expected => error => error.code === expected && !error.message.includes(tmpdir());
async function fixture(t) {
const root = await mkdtemp(path.join(tmpdir(), 'bot-files-'));
t.after(() => rm(root, {recursive: true, force: true}));
const service = new BotFileService(1, {root});
t.after(() => service.dispose());
return {root, service};
}
test('sorted trees preserve empty directories and binary chunks use a fixed listed size', async t => {
const {root, service} = await fixture(t);
await mkdir(path.join(root, 'empty'));
const binary = Buffer.alloc(300_000);
for (let i = 0; i < binary.length; i++) binary[i] = i % 256;
await writeFile(path.join(root, 'z.bin'), binary);
await writeFile(path.join(root, 'a'), 'a');
const tree = await service.list(deadline());
assert.equal(tree.partial, false);
assert.deepEqual(tree.children.map(x => x.name), ['a', 'empty', 'z.bin']);
assert.deepEqual(tree.children[1], {type: 'directory', name: 'empty', children: []});
assert.equal(JSON.stringify(tree).includes(root), false);
const id = tree.children[2].id;
const first = await service.read({id}, deadline());
const second = await service.read({id, offset: first.next_offset}, deadline());
assert.equal(first.next_offset, 262144);
assert.equal(first.eof, false);
assert.equal(second.eof, true);
assert.equal(second.total_bytes, binary.length);
assert.deepEqual(Buffer.concat([Buffer.from(first.data, 'base64'), Buffer.from(second.data, 'base64')]), binary);
const end = await service.read({id, offset: binary.length}, deadline());
assert.equal(end.data, '');
assert.equal(end.eof, true);
await writeFile(path.join(root, '..legal'), 'ok');
const legal = (await service.list(deadline())).children.find(entry => entry.name === '..legal');
assert.equal(Buffer.from((await service.read({id: legal.id}, deadline())).data, 'base64').toString(), 'ok');
});
test('IDs are scoped, expiring, opaque and request ranges are strict', async t => {
const {root} = await fixture(t);
let now = Date.now();
const service = new BotFileService(1, {root, now: () => now});
const other = new BotFileService(1, {root});
t.after(() => { service.dispose(); other.dispose(); });
await writeFile(path.join(root, 'file'), '123');
const tree = await service.list(now + 1000);
const id = tree.children[0].id;
assert.equal(tree.expires_at, now + 300_000);
await assert.rejects(other.read({id}, deadline()), code('INVALID_REQUEST'));
for (const payload of [{id: '../file'}, {id, offset: -1}, {id, offset: 4}, {id, length: 0},
{id, length: 262145}, {id, path: 'file'}, {id, offset: 0.5}]) {
await assert.rejects(service.read(payload, now + 1000), code('INVALID_REQUEST'));
}
now = tree.expires_at;
await assert.rejects(service.read({id}, now + 1000), code('INVALID_REQUEST'));
const limited = await service.list(now + 1000, now + 500);
assert.equal(limited.expires_at, now + 500);
now += 500;
await assert.rejects(service.read({id: limited.children[0].id}, now + 1000), code('INVALID_REQUEST'));
});
test('absent roots and empty files work; symlinks are excluded and unsafe roots rejected', async t => {
const {root, service} = await fixture(t);
const absent = new BotFileService(1, {root: path.join(root, 'absent')});
assert.deepEqual((await absent.list(deadline())).children, []);
await writeFile(path.join(root, 'empty'), '');
await symlink('empty', path.join(root, 'link'));
await symlink(root, path.join(root, 'loop'));
const tree = await service.list(deadline());
assert.deepEqual(tree.children.map(x => x.name), ['empty']);
assert.equal((await service.read({id: tree.children[0].id}, deadline())).data, '');
const unsafe = new BotFileService(1, {root: path.join(root, 'loop')});
await assert.rejects(unsafe.list(deadline()), code('RESYNC_REQUIRED'));
});
test('ordinary mutation, replacement, truncation and ancestor symlinks require resync', async t => {
const {root, service} = await fixture(t);
await mkdir(path.join(root, 'dir'));
const file = path.join(root, 'dir', 'file');
await writeFile(file, 'before');
const listedId = async () => (await service.list(deadline())).children[0].children[0].id;
let id = await listedId();
await writeFile(file, 'after');
await assert.rejects(service.read({id}, deadline()), code('RESYNC_REQUIRED'));
id = await listedId();
await rename(file, `${file}.old`);
await writeFile(file, 'after');
await assert.rejects(service.read({id}, deadline()), code('RESYNC_REQUIRED'));
id = await listedId();
await rename(path.join(root, 'dir'), path.join(root, 'saved'));
await symlink('saved', path.join(root, 'dir'));
await assert.rejects(service.read({id}, deadline()), code('RESYNC_REQUIRED'));
});
test('only direct log/*.log files may append, with fixed EOF; truncation and replacement reject', async t => {
const {root, service} = await fixture(t);
await mkdir(path.join(root, 'log'));
const file = path.join(root, 'log', 'bot.log');
await writeFile(file, 'first');
const id = (await service.list(deadline())).children[0].children[0].id;
await appendFile(file, 'second');
const data = await service.read({id}, deadline());
assert.equal(Buffer.from(data.data, 'base64').toString(), 'first');
assert.equal(data.total_bytes, 5);
assert.equal(data.eof, true);
await writeFile(file, 'x');
await assert.rejects(service.read({id}, deadline()), code('RESYNC_REQUIRED'));
await rename(file, `${file}.old`);
await writeFile(file, 'replacement');
await assert.rejects(service.read({id}, deadline()), code('RESYNC_REQUIRED'));
});
test('deadline, disposal and concurrency limits are checked during asynchronous operations', async t => {
const {root, service} = await fixture(t);
await writeFile(path.join(root, 'file'), 'data');
await assert.rejects(service.list(Date.now() - 1), code('TIMEOUT'));
const scan = service.list(deadline());
await assert.rejects(service.list(deadline()), code('RATE_LIMITED'));
const id = (await scan).children[0].id;
const reads = Array.from({length: 4}, () => service.read({id}, deadline()));
await assert.rejects(service.read({id}, deadline()), code('RATE_LIMITED'));
await Promise.all(reads);
const pending = service.read({id}, deadline());
service.dispose();
await assert.rejects(pending, code('CANCELLED'));
await assert.rejects(service.list(deadline()), code('CANCELLED'));
await assert.rejects(service.read({id}, deadline()), code('CANCELLED'));
const cancelled = new BotFileService(1, {root});
const listing = cancelled.list(deadline());
cancelled.dispose();
await assert.rejects(listing, code('CANCELLED'));
});
test('listings enforce node, depth and byte limits', async t => {
const {root, service} = await fixture(t);
await Promise.all(Array.from({length: 2010}, (_, i) => writeFile(path.join(root, `file-${i}`), '')));
const tree = await service.list(deadline());
assert.equal(tree.partial, true);
assert.ok(tree.children.length <= 2000);
assert.ok(Buffer.byteLength(JSON.stringify(tree)) <= 512 * 1024);
const firstId = tree.children[0].id;
for (let i = 0; i < 3; i++) await service.list(deadline());
const exhausted = await service.list(deadline());
assert.equal(exhausted.partial, true);
assert.deepEqual(exhausted.children, []);
assert.equal((await service.read({id: firstId}, deadline())).eof, true);
await rm(root, {recursive: true});
await mkdir(root);
let directory = root;
for (let i = 0; i < 34; i++) { directory = path.join(directory, 'd'); await mkdir(directory); }
const deep = await service.list(deadline());
let nodes = deep.children;
let depth = 0;
while (nodes.length) { depth++; nodes = nodes[0].children; }
assert.equal(depth, 32);
assert.equal(deep.partial, true);
});
test('byte bound truncates listings before the entry bound', async t => {
const {root, service} = await fixture(t);
await Promise.all(Array.from({length: 1800}, (_, i) =>
writeFile(path.join(root, `${String(i).padStart(4, '0')}-${'x'.repeat(240)}`), '')));
const tree = await service.list(deadline());
assert.equal(tree.partial, true);
assert.ok(tree.children.length > 0 && tree.children.length < 1800);
assert.ok(Buffer.byteLength(JSON.stringify(tree)) <= 512 * 1024);
});
test('deadline expiry during asynchronous work rejects and releases the scan slot', async t => {
const {root} = await fixture(t);
let now = Date.now();
const service = new BotFileService(1, {root, now: () => now});
t.after(() => service.dispose());
const end = now + 100;
const scan = service.list(end);
now = end;
await assert.rejects(scan, code('TIMEOUT'));
assert.deepEqual((await service.list(now + 100)).children, []);
await mkdir(path.join(root, 'log'));
await writeFile(path.join(root, 'log', 'ordinary.txt'), 'first');
const id = (await service.list(now + 100)).children[0].children[0].id;
await appendFile(path.join(root, 'log', 'ordinary.txt'), 'more');
await assert.rejects(service.read({id}, now + 100), code('RESYNC_REQUIRED'));
});