const assert = require('node:assert/strict'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const temporaryLogs = fs.mkdtempSync(path.join(os.tmpdir(), 'photobioreactor-')); process.env.LOGS_DIRECTORY = temporaryLogs; const { app, HISTORY_FILES } = require('../api/server'); let server; let baseUrl; test.before(async () => { await new Promise((resolve) => { server = app.listen(0, '127.0.0.1', resolve); }); const address = server.address(); baseUrl = `http://127.0.0.1:${address.port}`; }); test.after(async () => { await new Promise((resolve, reject) => { server.close((error) => error ? reject(error) : resolve()); }); fs.rmSync(temporaryLogs, { recursive: true, force: true }); }); test('returns simulated history for all sensors', async () => { const response = await fetch(`${baseUrl}/api/sensors/all`); const data = await response.json(); assert.equal(response.status, 200); assert.equal(data.length, 61); assert.ok(data[0].Timestamp); assert.ok(data[0].Temperatura_C); }); test('serves the dashboard in local development mode', async () => { const response = await fetch(`${baseUrl}/`); const html = await response.text(); assert.equal(response.status, 200); assert.match(html, /Panel del Fotobiorreactor/); }); test('validates sensor commands', async () => { const invalidResponse = await fetch(`${baseUrl}/api/sensors/invalid/command`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'r' }) }); assert.equal(invalidResponse.status, 400); const validResponse = await fetch(`${baseUrl}/api/sensors/ph/command`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'r' }) }); const validData = await validResponse.json(); assert.equal(validResponse.status, 200); assert.equal(validData.success, true); assert.equal(validData.mode, 'demo'); }); test('rejects undocumented calibration syntax', async () => { const response = await fetch(`${baseUrl}/api/sensors/do/command`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'Cal,atm' }) }); const data = await response.json(); assert.equal(response.status, 400); assert.match(data.error, /documentados/); }); test('validates simulated logging rates', async () => { const invalidResponse = await fetch(`${baseUrl}/api/config/logging`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rate: 3 }) }); assert.equal(invalidResponse.status, 400); const validResponse = await fetch(`${baseUrl}/api/config/logging`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rate: 5 }) }); const validData = await validResponse.json(); assert.equal(validResponse.status, 200); assert.equal(validData.rate, 5); assert.equal(validData.simulated, true); }); test('clears historical CSV files and preserves headers', async () => { for (const name of Object.keys(HISTORY_FILES)) { fs.writeFileSync(path.join(temporaryLogs, `${name}.csv`), 'old,data\n1,2\n'); } const response = await fetch(`${baseUrl}/api/history/clear`, { method: 'POST' }); assert.equal(response.status, 200); for (const [name, header] of Object.entries(HISTORY_FILES)) { const content = fs.readFileSync( path.join(temporaryLogs, `${name}.csv`), 'utf8' ); assert.equal(content, header); } });