import { test } from 'node:test'; import assert from 'node:assert/strict'; import { DatabaseSync } from 'node:sqlite'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { Readable } from 'node:stream'; import { createStudio, validateGraph } from './studio.mjs'; test('flows persist, simulation does not call TeamSpeak, credentials are hidden and readers cannot modify', async () => { const directory=mkdtempSync(join(tmpdir(),'botflow-studio-')); const db=new DatabaseSync(join(directory,'test.sqlite')); const calls=[]; const studio=createStudio(db,directory,{transport:async (connection,command,params)=>{ assert.equal(connection.apiKey,'secret-api-key'); calls.push({command,params}); return command==='serverinfo'?[{virtualserver_clientsonline:1,virtualserver_maxclients:32,virtualserver_channelsonline:8}]:command==='clientlist'?[{client_type:0,client_nickname:'Client',client_lastconnected:1}]:[]; }}); const invoke=async (path,method='GET',body={},role='Dono',origin='http://localhost:3080')=>{ const req=Readable.from(method==='GET'?[]:[Buffer.from(JSON.stringify(body))]);req.method=method;req.headers={origin};let result; await studio.route(req,new URL(path,'http://localhost:3080'),{role,name:'Tester'},(status,data)=>result={status,data});return result; }; try { const dashboard=(await invoke('/api/dashboard')).data,bot=dashboard.bots[0]; assert.equal(bot.enabled,false); assert.equal(dashboard.executions.length,0); assert.equal((await invoke('/api/bots','POST',{name:'Test'},'Leitor')).status,403); assert.equal((await invoke('/api/bots','POST',{name:'Test'},'Dono','http://evil.example')).status,403); const simulation=await invoke(`/api/bots/${bot.id}/simulate`,'POST'); assert.equal(simulation.data.status,'success');assert.match(simulation.data.output,/Pessoa A/);assert.equal(calls.length,0); assert.equal((await invoke(`/api/bots/${bot.id}/run`,'POST')).status,400); const connection=(await invoke('/api/connections','POST',{name:'Test',url:'http://teamspeak6:10080',serverId:1,apiKey:'secret-api-key'})).data; assert.equal(connection.hasApiKey,true);assert.equal(connection.apiKey,undefined); assert.ok(!db.prepare('SELECT data FROM studio_connections').get().data.includes('secret-api-key')); await invoke(`/api/bots/${bot.id}`,'PUT',{connectionId:connection.id,variables:{greeting:'Olá'}}); const execution=await invoke(`/api/bots/${bot.id}/run`,'POST'); assert.equal(execution.data.status,'success');assert.deepEqual(calls.map(c=>c.command),['serverinfo','clientlist','channeledit']);assert.equal(calls.at(-1).params.cid,21);assert.match(calls.at(-1).params.channel_description,/Client/); const modified=structuredClone(bot.graph);modified.edges.push({from:'n6',to:'n0',port:'out'}); assert.throws(()=>validateGraph(modified),/ciclo/); assert.equal((await invoke(`/api/bots/${bot.id}`,'PUT',{graph:modified})).status,400); const final=(await invoke('/api/dashboard')).data;assert.equal(final.executions.length,2);assert.equal(final.bots[0].variables.greeting,'Olá');assert.equal(final.connections[0].apiKey,undefined); } finally {studio.close();db.close();rmSync(directory,{recursive:true,force:true});} });