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, interpolate } from './studio.mjs'; test('templates subtract numeric Query counts without executing expressions', () => { const context = { server: { virtualserver_clientsonline:'6', virtualserver_queryclientsonline:'2' }, text:'Título' }; const formula = '{{server.virtualserver_clientsonline - server.virtualserver_queryclientsonline}}'; assert.equal(interpolate(`Online: ${formula} · {{text}}`, context), 'Online: 4 · Título'); context.server.virtualserver_queryclientsonline = 0; assert.equal(interpolate(formula, context), '6'); context.server.virtualserver_queryclientsonline = 'invalid'; assert.throws(() => interpolate(formula, context), /valor numérico/); delete context.server.virtualserver_queryclientsonline; assert.throws(() => interpolate(formula, context), /valor numérico/); assert.throws(() => interpolate('{{process.exit()}}', context), /não suportada/); assert.equal(interpolate('{{server.constructor}}', context), ''); }); 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:'3',virtualserver_queryclientsonline:'2',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.match(simulation.data.output, /Online: 3 \/ 32/); 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/); assert.match(calls.at(-1).params.channel_description, /Online: 1 \/ 32/); const modified=structuredClone(bot.graph);modified.edges.push({from:'n6',to:'n0',port:'out'}); assert.throws(()=>validateGraph(modified),/inválida|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});} });