view mrjunejune/test/conversation_api_test.js @ 263:ee04e4e69fed

Add functional JRPG frame and tools Add full-screen background_2 apertures, functional frame chrome, card-driven details, dual-window tools, live conversion workflows, and bounded cleanup for generated downloads. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Thu, 06 Aug 2026 11:31:30 -0700
parents b401627fc49e
children 04fee26ecce0
line wrap: on
line source

const assert = require('node:assert/strict');
const fs = require('node:fs');
const net = require('node:net');
const os = require('node:os');
const path = require('node:path');
const { spawn } = require('node:child_process');

const RUNFILES = process.env.JS_BINARY__RUNFILES;
const WORKSPACE = process.env.JS_BINARY__WORKSPACE;
const runfilesWorkspace = path.join(RUNFILES, WORKSPACE);

function findFreePort() {
  return new Promise((resolve, reject) => {
    const socket = net.createServer();
    socket.once('error', reject);
    socket.listen(0, '127.0.0.1', () => {
      const address = socket.address();
      socket.close(error => {
        if (error) reject(error);
        else resolve(String(address.port));
      });
    });
  });
}

async function waitForServer(child, baseUrl, logs) {
  const deadline = Date.now() + 15000;
  while (Date.now() < deadline) {
    if (child.exitCode !== null) {
      throw new Error(`Server exited with ${child.exitCode}\n${logs.join('')}`);
    }
    try {
      const response = await fetch(baseUrl);
      if (response.ok) return;
    } catch {
      // Keep waiting for startup.
    }
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  throw new Error(`Server startup timed out\n${logs.join('')}`);
}

async function stopProcess(child) {
  if (!child || child.exitCode !== null) return;
  child.kill('SIGTERM');
  await new Promise(resolve => {
    const timer = setTimeout(() => {
      if (child.exitCode === null) child.kill('SIGKILL');
    }, 3000);
    child.once('exit', () => {
      clearTimeout(timer);
      resolve();
    });
  });
}

(async () => {
  assert.ok(RUNFILES);
  assert.ok(WORKSPACE);
  const serverBinary = path.join(
    runfilesWorkspace,
    'mrjunejune/mrjunejune_server',
  );
  const fakeSidecar = path.join(
    runfilesWorkspace,
    'mrjunejune/test/inference_bridge_fake_sidecar',
  );
  const tempDirectory = fs.mkdtempSync(
    path.join(os.tmpdir(), 'mrjunejune-conversation-api-'),
  );
  const databasePath = path.join(tempDirectory, 'conversations.db');
  const port = await findFreePort();
  const baseUrl = `http://127.0.0.1:${port}`;
  const logs = [];
  let server;

  try {
    server = spawn(serverBinary, [], {
      cwd: runfilesWorkspace,
      env: {
        ...process.env,
        MRJUNEJUNE_PORT: port,
        MRJUNEJUNE_DB_PATH: databasePath,
        MRJUNEJUNE_INFERENCE_SIDECAR_PATH: fakeSidecar,
        MRJUNEJUNE_COPILOT_CLI_PATH: fakeSidecar,
        MRJUNEJUNE_ALLOW_ANONYMOUS_INFERENCE: '1',
      },
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    server.stdout.on('data', chunk => logs.push(chunk.toString()));
    server.stderr.on('data', chunk => logs.push(chunk.toString()));
    await waitForServer(server, baseUrl, logs);
    let response = await fetch(`${baseUrl}/api/inference/health`);
    assert.equal(response.status, 200);

    response = await fetch(`${baseUrl}/api/conversations`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://attacker.invalid',
      },
      body: JSON.stringify({ title: 'Blocked' }),
    });
    assert.equal(response.status, 403);

    response = await fetch(`${baseUrl}/api/conversations`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: baseUrl,
      },
      body: JSON.stringify({ title: 'First quest' }),
    });
    assert.equal(response.status, 201);
    const created = await response.json();
    assert.match(created.id, /^[0-9a-f-]{36}$/);

    response = await fetch(`${baseUrl}/api/conversations/${created.id}`);
    assert.equal(response.status, 200);
    let conversation = await response.json();
    assert.equal(conversation.title, 'First quest');
    assert.deepEqual(conversation.turns, []);

    response = await fetch(`${baseUrl}/api/conversations/${created.id}`, {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json',
        Origin: baseUrl,
      },
      body: JSON.stringify({ title: 'Renamed quest' }),
    });
    assert.equal(response.status, 200);

    response = await fetch(
      `${baseUrl}/api/conversations/${created.id}/turns`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Origin: baseUrl,
        },
        body: JSON.stringify({ prompt: 'Hello Epi' }),
      },
    );
    assert.equal(response.status, 200);
    assert.match(
      response.headers.get('content-type') || '',
      /^text\/event-stream/,
    );
    const stream = await response.text();
    assert.match(stream, /event: turn\.accepted/);
    assert.match(stream, /event: tool\.started/);
    assert.match(stream, /"tool":\{"name":"mock-search"\}/);
    assert.match(stream, /event: assistant\.delta/);
    assert.match(stream, /hello \\"traveler\\"\\\\path/);
    assert.match(stream, /event: assistant\.completed/);
    assert.match(stream, /event: assistant\.usage/);
    assert.match(stream, /event: turn\.done/);

    response = await fetch(`${baseUrl}/api/conversations/${created.id}`);
    conversation = await response.json();
    assert.equal(conversation.title, 'Renamed quest');
    assert.equal(conversation.turns.length, 2);
    assert.equal(conversation.turns[0].content, 'Hello Epi');
    assert.equal(conversation.turns[1].content, 'hello traveler');
    assert.equal(conversation.turns[1].input_tokens, 7);
    assert.equal(conversation.turns[1].output_tokens, 3);

    response = await fetch(`${baseUrl}/api/conversations/${created.id}`, {
      method: 'DELETE',
      headers: { Origin: baseUrl },
    });
    assert.equal(response.status, 204);
    response = await fetch(`${baseUrl}/api/conversations/${created.id}`);
    assert.equal(response.status, 404);
  } finally {
    await stopProcess(server);
    fs.rmSync(tempDirectory, { recursive: true, force: true });
  }
})().catch(error => {
  console.error(error.stack || error);
  process.exitCode = 1;
});