Ctrl K
ComunidadGuías…

scripts/smoke-desktop.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { spawn } from 'node:child_process';
import { mkdtemp, readFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import assert from 'node:assert/strict';
import { createServer } from 'node:net';
import { setTimeout as delay } from 'node:timers/promises';

const binary = resolve(process.argv[2] || 'dists/local/rowset');
const directory = await mkdtemp(join(tmpdir(), 'rowset-desktop-smoke-'));
const env = { ...process.env, ROWSET_DESKTOP_DIR: directory, ROWSET_DESKTOP_NO_BROWSER: '1' };
let child = spawn(binary, ['desktop'], { env, stdio: 'pipe' });
let occupiedPort;
let output = '';
child.stderr.on('data', chunk => { output += chunk; });
try {
  let state;
  for (let attempt = 0; attempt < 100; attempt++) {
    if (child.exitCode !== null) throw new Error(`Desktop exited: ${output}`);
    try { state = JSON.parse(await readFile(join(directory, 'instance.json'), 'utf8')); if ((await fetch(`http://127.0.0.1:${state.port}/readyz`)).ok) break; } catch { /* starting */ }
    await delay(100);
  }
  assert(state, 'desktop never became ready');
  const base = `http://127.0.0.1:${state.port}`;
  const open = await fetch(base + '/api/local/open', { method: 'POST', headers: { Authorization: `Bearer ${state.key}` } });
  assert.equal(open.status, 200);
  const ticket = await open.json();
  const login = () => fetch(base + '/api/auth/local', { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: base }, body: JSON.stringify(ticket) });
  const session = await login(); assert.equal(session.status, 200);
  assert.equal((await login()).status, 401, 'ticket replay accepted');
  const user = await session.json(); assert(user.accessToken);
  const workspace = { version: 1, activeTabId: 'smoke-tab', tabs: [{ id: 'smoke-tab', title: 'Port restart', sql: "SELECT 'workspace-survives-restart'", database: 'CaseDb' }] };
  const saveWorkspace = await fetch(base + '/api/workspace', { method: 'PUT', headers: { Authorization: `Bearer ${user.accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ revision: 0, document: workspace }) });
  assert.equal(saveWorkspace.status, 200, 'workspace save failed');
  const second = spawn(binary, ['desktop'], { env, stdio: 'pipe' });
  const secondStatus = await new Promise((resolve, reject) => { second.on('error', reject); second.on('exit', resolve); });
  assert.equal(secondStatus, 0, 'second launch failed');
  const sameState = JSON.parse(await readFile(join(directory, 'instance.json'), 'utf8'));
  assert.equal(sameState.port, state.port); assert.equal(sameState.key, state.key, 'second launch replaced the instance');
  const absent = await fetch(base + '/api/audit', { headers: { Authorization: `Bearer ${user.accessToken}` } }); assert.equal(absent.status, 404, 'server-only route answered');
  const stop = await fetch(base + '/api/local/stop?rollback=true', { method: 'POST', headers: { Authorization: `Bearer ${state.key}` } }); assert.equal(stop.status, 204);
  for (let attempt = 0; child.exitCode === null && attempt < 100; attempt++) await delay(100);
  assert.equal(child.exitCode, 0, 'desktop did not stop gracefully');
  // Force the next launcher onto a different port without touching user data.
  occupiedPort = createServer(socket => socket.end('HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n'));
  await new Promise((resolve, reject) => { occupiedPort.once('error', reject); occupiedPort.listen(state.port, '127.0.0.1', resolve); });
  child = spawn(binary, ['desktop'], { env, stdio: 'pipe' });
  child.stderr.on('data', chunk => { output += chunk; });
  let restarted;
  for (let attempt = 0; attempt < 100; attempt++) {
    if (child.exitCode !== null) throw new Error(`Restart exited: ${output}`);
    try {
      const candidate = JSON.parse(await readFile(join(directory, 'instance.json'), 'utf8'));
      if (candidate.port !== state.port && (await fetch(`http://127.0.0.1:${candidate.port}/readyz`)).ok) { restarted = candidate; break; }
    } catch { /* starting */ }
    await delay(100);
  }
  assert(restarted, 'port collision restart never became ready');
  const restartedBase = `http://127.0.0.1:${restarted.port}`;
  const nextTicket = await fetch(restartedBase + '/api/local/open', { method: 'POST', headers: { Authorization: `Bearer ${restarted.key}` } });
  const nextLogin = await fetch(restartedBase + '/api/auth/local', { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: restartedBase }, body: JSON.stringify(await nextTicket.json()) });
  assert.equal(nextLogin.status, 200);
  const nextUser = await nextLogin.json();
  const restored = await fetch(restartedBase + '/api/workspace', { headers: { Authorization: `Bearer ${nextUser.accessToken}` } });
  assert.equal(restored.status, 200);
  const restoredWorkspace = await restored.json();
  assert.equal(restoredWorkspace.revision, 1); assert.deepEqual(restoredWorkspace.document, workspace);
  const staleSave = await fetch(restartedBase + '/api/workspace', { method: 'PUT', headers: { Authorization: `Bearer ${nextUser.accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ revision: 0, document: workspace }) });
  assert.equal(staleSave.status, 409, 'stale revision overwrote saved workspace');
  const finalStop = await fetch(restartedBase + '/api/local/stop?rollback=true', { method: 'POST', headers: { Authorization: `Bearer ${restarted.key}` } }); assert.equal(finalStop.status, 204);
  for (let attempt = 0; child.exitCode === null && attempt < 100; attempt++) await delay(100);
  assert.equal(child.exitCode, 0, 'restarted desktop did not stop gracefully');
  console.log('PASS: automatic setup, one-use login, single instance, no server-only routes, shutdown, port collision restart, durable workspace and stale-write rejection');
  console.log(`Temporary test workspace retained: ${directory}`);
} finally {
  occupiedPort?.close();
  if (child.exitCode === null) child.kill('SIGTERM');
}