Files
LTX-DeskWEB/server/app.test.mjs
T

93 lines
7.0 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import http from 'node:http'
import { mkdtemp, mkdir, writeFile, readFile, symlink, rm, stat, chmod } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { createApp } from './app.mjs'
const password='test-only-password-123456'
async function fixture(t) {
const dir=await mkdtemp(path.join(os.tmpdir(),'deskweb-test-'))
await mkdir(path.join(dir,'dist'));await writeFile(path.join(dir,'dist/index.html'),'<html>DeskWEB test</html>')
await mkdir(path.join(dir,'output'))
const requests=[]
const upstream=http.createServer(async(req,res)=>{
const chunks=[];for await(const chunk of req)chunks.push(chunk)
requests.push({url:req.url,method:req.method,authorization:req.headers.authorization,body:Buffer.concat(chunks).toString()})
res.setHeader('Content-Type','application/json');res.setHeader('Set-Cookie','upstream-secret=hidden')
if(req.url==='/api/fail'){res.writeHead(422);res.end('{"detail":"native validation"}');return}
res.end('{"status":"ok"}')
})
await new Promise(r=>upstream.listen(0,'127.0.0.1',r))
const app=await createApp({password,backendUrl:`http://127.0.0.1:${upstream.address().port}`,backendToken:'test-upstream-token',dist:path.join(dir,'dist'),roots:[{local:path.join(dir,'input'),backend:'/ltx/inputs',writable:true},{local:path.join(dir,'output'),backend:'/ltx/outputs'}],uploadLimit:1024})
await new Promise(r=>app.listen(0,'127.0.0.1',r))
t.after(async()=>{app.closeAllConnections();upstream.closeAllConnections();await Promise.all([new Promise(r=>app.close(r)),new Promise(r=>upstream.close(r))]);await rm(dir,{recursive:true,force:true})})
const base=`http://127.0.0.1:${app.address().port}`
const login=await fetch(base+'/web/login',{method:'POST',headers:{'X-Deskweb-Request':'1'},body:JSON.stringify({password})})
assert.equal(login.status,200)
const cookie=login.headers.get('set-cookie').split(';')[0]
const call=(route,init={})=>fetch(base+route,{...init,headers:{Cookie:cookie,'X-Deskweb-Request':'1',...init.headers}})
return {dir,base,call,requests,cookie}
}
test('sign-in required; secret never returned; logout invalidates session',async t=>{
const {base,call}=await fixture(t)
assert.equal((await fetch(base+'/web/session')).status,401)
const session=await call('/web/session');assert.equal(session.status,200);assert.ok(!(await session.text()).includes('test-upstream-token'))
assert.equal((await call('/web/logout',{method:'POST'})).status,200)
assert.equal((await call('/web/session')).status,401)
})
test('native API bytes, query, method and validation errors pass through; browser token replaced',async t=>{
const {call,requests}=await fixture(t)
const body='{"prompt":"synthetic test","width":768,"custom_native_field":true}'
const response=await call('/ltx/api/generate?native=1',{method:'POST',headers:{Authorization:'Bearer browser-value','Content-Type':'application/json'},body})
assert.equal(response.status,200);assert.equal(response.headers.get('set-cookie'),null)
assert.deepEqual(requests[0],{url:'/api/generate?native=1',method:'POST',authorization:'Bearer test-upstream-token',body})
const invalid=await call('/ltx/api/fail');assert.equal(invalid.status,422);assert.equal(await invalid.text(),'{"detail":"native validation"}')
})
test('reject cross-site and arbitrary upstream routes',async t=>{
const {call}=await fixture(t)
assert.equal((await call('/ltx/api/generate',{method:'POST',headers:{Origin:'https://evil.example'}})).status,403)
assert.equal((await call('/web/media?path=/ltx/outputs/a.mp4',{headers:{'Sec-Fetch-Site':'cross-site'}})).status,403)
assert.equal((await call('/ltx//evil.example/api/generate')).status,404)
})
test('upload, backend path, range playback and existence check',async t=>{
const {call,dir}=await fixture(t)
const upload=await call('/web/upload?name=clip.mp4',{method:'POST',body:'0123456789'})
assert.equal(upload.status,201);const result=await upload.json();assert.ok(result.path.startsWith('/ltx/inputs/'))
assert.equal((await stat(path.join(dir,'input',path.basename(result.path)))).mode & 0o777,0o640)
assert.equal(await readFile(path.join(dir,'input',path.basename(result.path)),'utf8'),'0123456789')
const media=await call('/web/media?path='+encodeURIComponent(result.path),{headers:{Range:'bytes=2-5'}})
assert.equal(media.status,206);assert.equal(media.headers.get('content-range'),'bytes 2-5/10');assert.equal(await media.text(),'2345')
const exists=await call('/web/files',{method:'POST',body:JSON.stringify({action:'exists',paths:[result.path,'/etc/passwd']})})
assert.deepEqual(await exists.json(),{[result.path]:true,'/etc/passwd':false})
})
test('prevent file traversal, symlink escape, active file uploads, oversized uploads',async t=>{
const {call,dir}=await fixture(t)
await writeFile(path.join(dir,'secret.mp4'),'hidden');await symlink(path.join(dir,'secret.mp4'),path.join(dir,'output','link.mp4'))
for(const p of ['/etc/passwd','/ltx/outputs/../../secret.mp4','/ltx/outputs/link.mp4'])assert.equal((await call('/web/media?path='+encodeURIComponent(p))).status,403)
assert.equal((await call('/web/upload?name=script.html',{method:'POST',body:'<script/>'})).status,400)
assert.equal((await call('/web/upload?name=big.mp4',{method:'POST',body:'x'.repeat(1025)})).status,413)
})
test('login rate limit',async t=>{
const {base}=await fixture(t)
for(let i=0;i<10;i++)assert.equal((await fetch(base+'/web/login',{method:'POST',headers:{'X-Deskweb-Request':'1'},body:JSON.stringify({password:'wrong'})})).status,401)
assert.equal((await fetch(base+'/web/login',{method:'POST',headers:{'X-Deskweb-Request':'1'},body:JSON.stringify({password})})).status,429)
})
test('shared media can be inspected, copied and thumbnailed without an inference runtime',async t=>{
const {call,dir}=await fixture(t)
// Deliberately tiny synthetic PPM pixels; ffprobe detects content, not extension.
await writeFile(path.join(dir,'output','test.png'),Buffer.concat([Buffer.from('P6\n2 2\n255\n'),Buffer.from([0,0,255,0,0,255,0,0,255,0,0,255])]))
await chmod(path.join(dir,'output','test.png'),0o600)
const invoke=async action=>{
const response=await call('/web/files',{method:'POST',body:JSON.stringify({action,path:'/ltx/outputs/test.png'})})
assert.equal(response.status,200);return response.json()
}
const metadata=await invoke('metadata');assert.equal(metadata.width,2);assert.equal(metadata.height,2)
const copy=await invoke('copy');assert.ok(copy.path.startsWith('/ltx/inputs/'))
assert.equal((await stat(path.join(dir,'input',path.basename(copy.path)))).mode & 0o777,0o640)
const thumbnail=await invoke('thumbnail');assert.ok(thumbnail.path.endsWith('.jpg'))
assert.equal((await stat(path.join(dir,'input',path.basename(thumbnail.path)))).mode & 0o777,0o640)
const response=await call('/web/media?path='+encodeURIComponent(thumbnail.path));assert.equal(response.status,200)
assert.equal(response.headers.get('content-type'),'image/jpeg');assert.ok((await response.arrayBuffer()).byteLength>100)
})