85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
import {
|
|
mkdir,
|
|
mkdtemp,
|
|
rm,
|
|
symlink,
|
|
writeFile,
|
|
} from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
// Point boxHomePaths('t').homeDir at a real tmp home, then import box fresh so
|
|
// env.workdir (captured at import) reflects the tmp workdir for this test.
|
|
const loadWithHome = async () => {
|
|
const workdir = await mkdtemp(path.join(os.tmpdir(), 'spoon-box-'));
|
|
tempDirs.push(workdir);
|
|
vi.resetModules();
|
|
process.env.SPOON_WORKER_TOKEN = 'test-worker-token';
|
|
process.env.GITHUB_APP_ID = '123';
|
|
process.env.GITHUB_APP_PRIVATE_KEY =
|
|
'-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----';
|
|
process.env.SPOON_AGENT_WORKDIR = workdir;
|
|
const box = await import('../../src/box');
|
|
const homeDir = box.boxHomePaths('t').homeDir;
|
|
await mkdir(homeDir, { recursive: true });
|
|
return { box, homeDir, workdir };
|
|
};
|
|
|
|
describe('box file helpers', () => {
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
tempDirs.map((dir) => rm(dir, { force: true, recursive: true })),
|
|
);
|
|
tempDirs.length = 0;
|
|
vi.resetModules();
|
|
});
|
|
|
|
test('writeBoxFile then readBoxFile round-trips', async () => {
|
|
const { box } = await loadWithHome();
|
|
await expect(
|
|
box.writeBoxFile('t', 'notes/todo.txt', 'hello box'),
|
|
).resolves.toEqual({ success: true });
|
|
await expect(box.readBoxFile('t', 'notes/todo.txt')).resolves.toBe(
|
|
'hello box',
|
|
);
|
|
});
|
|
|
|
test('readBoxFile rejects a lexical .. escape', async () => {
|
|
const { box } = await loadWithHome();
|
|
await expect(
|
|
box.readBoxFile('t', '../../etc/passwd'),
|
|
).rejects.toThrow(/outside home/);
|
|
});
|
|
|
|
test('rejects a symlink inside home that points outside on read and write', async () => {
|
|
const { box, homeDir, workdir } = await loadWithHome();
|
|
const outside = path.join(workdir, 'outside');
|
|
await mkdir(outside, { recursive: true });
|
|
await writeFile(path.join(outside, 'secret'), 'S');
|
|
await symlink(path.join(outside, 'secret'), path.join(homeDir, 'link'));
|
|
|
|
await expect(box.readBoxFile('t', 'link')).rejects.toThrow(/outside home/);
|
|
await expect(
|
|
box.writeBoxFile('t', 'link', 'nope'),
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
test('listBoxTree includes a created file and excludes node_modules', async () => {
|
|
const { box, homeDir } = await loadWithHome();
|
|
await writeFile(path.join(homeDir, 'readme.md'), '# hi');
|
|
await mkdir(path.join(homeDir, 'node_modules', 'pkg'), { recursive: true });
|
|
await writeFile(path.join(homeDir, 'node_modules', 'pkg', 'x.js'), '1');
|
|
|
|
const tree = await box.listBoxTree('t');
|
|
expect(tree.name).toBe('~');
|
|
expect(tree.path).toBe('');
|
|
expect(tree.type).toBe('directory');
|
|
const names = (tree.children ?? []).map((child) => child.name);
|
|
expect(names).toContain('readme.md');
|
|
expect(names).not.toContain('node_modules');
|
|
});
|
|
});
|