patx/relay-lang

import { spawn } from "node:child_process";
import { mkdtemp, open } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import assert from "node:assert/strict";

const directory = await mkdtemp(join(tmpdir(), "relay-integration-"));
const database = `relay_integration_${Date.now()}`;
const processes = [];
const logs = [];
const ports = [29187, 29188];
try {
  for (const port of ports) {
    const log = await open(join(directory, `${port}.jsonl`), "w"); logs.push(log);
    const child = spawn(resolve(process.env.RELAY_BINARY || "target/release/relay"), ["run", "examples/twitter_clone/app.ry"], {
      env: { ...process.env, RELAY_ENV: "development", RELAY_BIND: `127.0.0.1:${port}`,
        RELAY_TRACE: process.argv.includes("--trace") ? "1" : "0",
        RELAY_MONGO_URI: process.env.RELAY_TEST_MONGO_URI || "mongodb://127.0.0.1:27187", RELAY_MONGO_DATABASE: database },
      stdio: ["ignore", "ignore", log.fd],
    });
    const stopped = new Promise(resolve => child.on("exit", (code, signal) => resolve({ code, signal })));
    processes.push({ child, stopped });
    let ready = false;
    for (let attempt = 0; attempt < 150 && child.exitCode === null; attempt++) {
      try { ready = (await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(1000) })).ok; } catch {}
      if (ready) break;
      await delay(200);
    }
    assert(ready, `Server ${port} failed to start; see ${directory}`);
  }
  const client = () => ({ cookie: "", csrf: "" });
  async function request(user, port, path, body, expected = 200, token = true) {
    const headers = { cookie: user.cookie };
    if (token && user.csrf) headers["x-csrf-token"] = user.csrf;
    if (body) headers["content-type"] = "application/json";
    const response = await fetch(`http://127.0.0.1:${port}${path}`, { method: body ? "POST" : "GET", headers,
      body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(10000) });
    const raw = await response.text();
    assert.equal(response.status, expected, `${path}: ${raw}`);
    const cookie = response.headers.get("set-cookie");
    if (cookie) user.cookie = cookie.split(";")[0];
    const data = JSON.parse(raw);
    if (data.csrf_token) user.csrf = data.csrf_token;
    return data;
  }
  const ada = client(), bob = client();
  assert.equal((await request(ada, ports[0], "/api/me")).authenticated, false);
  const preLoginCookie = ada.cookie;
  await request(ada, ports[0], "/api/auth/signup", { username: "ada", password: "test-password", bio: "Hello" });
  assert.notEqual(ada.cookie, preLoginCookie, "Login must rotate the session");
  assert.equal((await request(ada, ports[1], "/api/me")).user.username, "ada", "Session must work on another process");
  await request(ada, ports[1], "/api/posts", { body: "CSRF must fail" }, 403, false);
  await request(ada, ports[1], "/api/posts", { body: "Hello from Relay" });
  await request(bob, ports[0], "/api/me");
  await request(bob, ports[0], "/api/auth/signup", { username: "bob", password: "test-password", bio: "Hi" });
  await request(bob, ports[1], "/api/me");
  await request(bob, ports[1], "/api/users/ada/follow", {});
  const feed = await request(bob, ports[0], "/api/feed/home");
  assert(feed.items.some(post => post.body === "Hello from Relay"));
  await Promise.all(Array.from({ length: 32 }, (_, index) => request(index % 2 ? ada : bob, ports[index % 2], "/api/me")
    .then(data => assert.equal(data.user.username, index % 2 ? "ada" : "bob"))));
  await request(bob, ports[0], "/api/users/ada/unfollow", {});
  const oldCookie = ada.cookie;
  await request(ada, ports[1], "/api/auth/logout", {});
  assert.equal((await request({ cookie: oldCookie, csrf: ada.csrf }, ports[0], "/api/me")).authenticated, false);
  await request(ada, ports[0], "/api/me");
  await request(ada, ports[0], "/api/auth/login", { username: "ada", password: "test-password" });
  assert.equal((await request(ada, ports[1], "/api/me")).user.username, "ada");
  console.log(`Integration passed: two processes, persistent sessions, CSRF, signup/login/logout, posts, follows and feeds. Logs: ${directory}; isolated database: ${database}`);
} finally {
  for (const { child } of processes) if (child.exitCode === null) child.kill("SIGTERM");
  for (const { child, stopped } of processes) {
    const result = await Promise.race([stopped, delay(35000, null, { ref: false })]);
    if (!result) { child.kill("SIGKILL"); throw new Error("Graceful shutdown failed"); }
    assert.equal(result.code, 0, `Server shutdown: ${JSON.stringify(result)}`);
  }
  for (const log of logs) await log.close();
}