| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 |
- import fs from 'node:fs'
- import path from 'node:path'
- import { describe, expect, it } from 'vitest'
- import { config } from '../src/config'
- import { execFileSync } from 'node:child_process'
- import { convertDocument, recalcWorkbook, sandboxArgs } from '../src/services/office'
- // 真实调用 LibreOffice(约数秒),验证独立沙箱中的转换可用
- describe('convertDocument', () => {
- const ws = path.join(config.dataDir, 'workspaces/ses_office')
- fs.mkdirSync(ws, { recursive: true })
- fs.writeFileSync(path.join(ws, '中文 报告.txt'), '你好,LibreOffice')
- it('把工作区内的文件转换为 PDF(中文文件名)', async () => {
- const out = await convertDocument({ workspaceDir: ws, input: '中文 报告.txt', format: 'pdf' })
- expect(out).toBe(path.join(fs.realpathSync(ws), 'outputs/中文 报告.pdf'))
- expect(fs.readFileSync(out).subarray(0, 4).toString()).toBe('%PDF')
- }, 120_000)
- it('拒绝工作区以外的输入和输出', async () => {
- await expect(convertDocument({ workspaceDir: ws, input: '/etc/hostname', format: 'pdf' })).rejects.toThrow(/工作区内/)
- await expect(convertDocument({ workspaceDir: ws, input: '中文 报告.txt', format: 'pdf', outDir: '/tmp' })).rejects.toThrow(/工作区内/)
- })
- })
- describe('转换沙箱隔离', () => {
- const ws = path.join(config.dataDir, 'workspaces/ses_iso')
- fs.mkdirSync(ws, { recursive: true })
- const run = (cmd: string) =>
- execFileSync('bwrap', [...sandboxArgs(fs.realpathSync(ws)), 'sh', '-c', cmd], { encoding: 'utf8' }).trim()
- it('看不到 home、/var、/srv、/run,也连不上宿主 socket', () => {
- expect(run('for d in /home /srv /run /mnt /root; do [ -e $d ] && echo $d; done; true')).toBe('')
- // /var 中只有字体缓存
- expect(run('find /var -mindepth 1 -maxdepth 2 | sort')).toBe('/var/cache\n/var/cache/fontconfig')
- expect(run('[ -S /var/run/docker.sock ] || [ -S /var/snap/lxd/common/lxd/unix.socket ] && echo found || echo none')).toBe('none')
- })
- it('/usr 与 /etc 中的 socket 被屏蔽', () => {
- // find 按目录项类型报告,需要 stat 实际 inode:被 /dev/null 覆盖后应为字符设备
- expect(run('find /usr/share /usr/local /etc -type s 2>/dev/null | while read f; do LC_ALL=C stat -c %F "$f"; done | grep -c socket || true')).toBe('0')
- })
- it('断网', () => {
- expect(run('cat /sys/class/net/*/operstate 2>/dev/null | grep -c up || true')).toBe('0')
- })
- })
- describe('recalcWorkbook', () => {
- const ws = path.join(config.dataDir, 'workspaces/ses_recalc')
- fs.mkdirSync(path.join(ws, '.agent'), { recursive: true })
- const py = '/srv/agent-server/runtime/py'
- it('重算公式并报告错误单元格', async () => {
- // 用 openpyxl 写入公式(无缓存值),包含一个除零错误
- execFileSync(path.join(py, 'bin/python'), ['-c', `
- import openpyxl
- wb = openpyxl.Workbook(); s = wb.active; s.title = '数据'
- s['A1'] = 10; s['A2'] = 32; s['A3'] = '=SUM(A1:A2)'; s['B1'] = '=A1/0'
- wb.save('${ws}/book.xlsx')`])
- const r = await recalcWorkbook({ workspaceDir: ws, input: 'book.xlsx', pythonRuntime: py })
- expect(r.formulas).toBe(2)
- expect(r.errors).toEqual([{ sheet: '数据', cell: 'B1', error: '#DIV/0!' }])
- const value = execFileSync(path.join(py, 'bin/python'), ['-c', `import openpyxl; print(openpyxl.load_workbook('${ws}/book.xlsx', data_only=True).active['A3'].value)`], { encoding: 'utf8' })
- expect(value.trim()).toBe('42')
- }, 120_000)
- })
|