noxid-cli 0.2.1

The Noxid compiler command line: check, build, test, adapt, and the agent surface
use std::fs;
use std::path::PathBuf;
use std::process::{Command, Output};
use std::sync::atomic::{AtomicU64, Ordering};

static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);

struct Fixture {
    root: PathBuf,
}

impl Fixture {
    fn new(label: &str) -> Self {
        let ordinal = NEXT_FIXTURE.fetch_add(1, Ordering::Relaxed);
        let root = std::env::temp_dir().join(format!(
            "noxid-wo24-qa3-{label}-{}-{ordinal}",
            std::process::id()
        ));
        fs::create_dir_all(root.join("server/queues")).expect("create queue fixture");
        fs::write(
            root.join("Noxid.toml"),
            "[app]\ntitle = \"WO-24 QA round 3\"\n",
        )
        .expect("write project manifest");
        fs::write(root.join("package.json"), "{\"type\":\"module\"}\n")
            .expect("write ESM package marker");
        fs::write(
            root.join("server/host.js"),
            "export const queues = Object.freeze({});\n",
        )
        .expect("write inert host");
        Self { root }
    }

    fn write(&self, relative: &str, contents: &str) {
        let path = self.root.join(relative);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).expect("create fixture parent");
        }
        fs::write(path, contents).expect("write fixture file");
    }

    fn noxid(&self, arguments: &[&str]) -> Output {
        Command::new(env!("CARGO_BIN_EXE_noxid"))
            .args(arguments)
            .current_dir(&self.root)
            .output()
            .expect("run noxid")
    }

    fn build(&self) -> Output {
        self.noxid(&["build", ".", "--out-dir", "dist"])
    }

    fn run_node(&self, name: &str, source: &str) -> Output {
        self.write(&format!("dist/{name}.mjs"), source);
        Command::new("node")
            .arg(format!("{name}.mjs"))
            .current_dir(self.root.join("dist"))
            .env_remove("DATABASE_URL")
            .output()
            .expect("execute generated queue runtime")
    }
}

impl Drop for Fixture {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.root);
    }
}

fn output_text(output: &Output) -> String {
    format!(
        "stdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    )
}

fn assert_success(output: &Output, context: &str) {
    assert!(
        output.status.success(),
        "{context}:\n{}",
        output_text(output)
    );
}

#[test]
fn qa_round3_nested_named_payloads_remain_closed_and_effect_free() {
    let fixture = Fixture::new("nested-ordinary-data");
    fixture.write(
        "server/queues/Deliver.nox",
        r#"type Receipt { id: String note: Optional<String> }
queue Deliver {
    payload { receipt: Receipt }
    retry: 1
    backoff: 1s
}
"#,
    );
    assert_success(&fixture.build(), "build nested ordinary-data fixture");

    let node = fixture.run_node(
        "nested-ordinary-data",
        r#"import { enqueue } from "./server/handler.js";
async function code(receipt) {
  try { await enqueue("Deliver", { receipt }); } catch (error) { return error?.code ?? error?.name; }
  return null;
}
let reads = 0;
const accessor = { note: null };
Object.defineProperty(accessor, "id", { enumerable: true, get() { reads += 1; return "effect"; } });
const inherited = Object.create({ id: "inherited" });
inherited.note = null;
const cases = [
  ["valid", { id: "r-1", note: null }, "QUEUE_DATABASE_URL_REQUIRED"],
  ["accessor", accessor, "QUEUE_PAYLOAD_TYPE"],
  ["inherited", inherited, "QUEUE_PAYLOAD_TYPE"],
  ["extra", { id: "r-2", note: null, admin: true }, "QUEUE_PAYLOAD_TYPE"],
];
const failures = [];
for (const [label, receipt, expected] of cases) {
  const observed = await code(receipt);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (reads !== 0) failures.push(`nested id getter executed ${reads} time(s)`);
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(&node, "enforce closed nested named payloads");
}

#[test]
fn qa_round3_array_accessors_and_cycles_fail_with_a_structured_queue_code() {
    let fixture = Fixture::new("array-ordinary-data");
    fixture.write(
        "server/queues/Matrix.nox",
        "queue Matrix { payload { values: Array<Array<Int>> } retry: 0 backoff: 1s }\n",
    );
    assert_success(&fixture.build(), "build array ordinary-data fixture");

    let node = fixture.run_node(
        "array-ordinary-data",
        r#"import { enqueue } from "./server/handler.js";
async function code(values) {
  try { await enqueue("Matrix", { values }); } catch (error) { return error?.code ?? error?.name; }
  return null;
}
let reads = 0;
const accessor = Array(1);
Object.defineProperty(accessor, 0, { enumerable: true, get() { reads += 1; return [7]; } });
const cyclic = [];
cyclic.push(cyclic);
const accessorCode = await code(accessor);
const cyclicCode = await code(cyclic);
const failures = [];
if (accessorCode !== "QUEUE_PAYLOAD_TYPE") failures.push(`accessor array: expected QUEUE_PAYLOAD_TYPE, saw ${accessorCode}`);
if (cyclicCode !== "QUEUE_PAYLOAD_TYPE") failures.push(`cyclic array: expected QUEUE_PAYLOAD_TYPE, saw ${cyclicCode}`);
if (reads !== 0) failures.push(`array getter executed ${reads} time(s)`);
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(&node, "reject effectful and cyclic queue arrays");
}

#[test]
fn qa_round3_date_payloads_require_exact_utc_instants() {
    let fixture = Fixture::new("date-payload");
    fixture.write(
        "server/queues/Schedule.nox",
        "queue Schedule { payload { at: Date } retry: 0 backoff: 1s }\n",
    );
    assert_success(&fixture.build(), "build Date payload fixture");

    let node = fixture.run_node(
        "date-payload",
        r#"import { enqueue } from "./server/handler.js";
async function code(at) {
  try { await enqueue("Schedule", { at }); } catch (error) { return error?.code ?? error?.name; }
  return null;
}
const cases = [
  ["exact", "2028-02-29T23:59:59.123Z", "QUEUE_DATABASE_URL_REQUIRED"],
  ["impossible", "2026-02-31T12:00:00Z", "QUEUE_PAYLOAD_TYPE"],
  ["human", "August 30, 2026 12:00:00 UTC", "QUEUE_PAYLOAD_TYPE"],
  ["offset", "2026-08-30T12:00:00+00:00", "QUEUE_PAYLOAD_TYPE"],
];
const failures = [];
for (const [label, at, expected] of cases) {
  const observed = await code(at);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(&node, "validate persisted Date payloads as UTC instants");
}

#[test]
fn qa_round3_prior_run_at_option_blocker_stays_closed_at_more_boundaries() {
    let fixture = Fixture::new("run-at-replay");
    fixture.write(
        "server/queues/Safe.nox",
        "queue Safe { payload {} retry: 0 backoff: 1s }\n",
    );
    assert_success(&fixture.build(), "build runAt replay fixture");

    let node = fixture.run_node(
        "run-at-replay",
        r#"import { enqueue } from "./server/handler.js";
async function code(options) {
  try { await enqueue("Safe", {}, options); } catch (error) { return error?.code ?? error?.name; }
  return null;
}
let reads = 0;
const accessor = {};
Object.defineProperty(accessor, "runAt", { enumerable: true, get() { reads += 1; return "2028-02-29T12:00:00Z"; } });
const nonEnumerable = {};
Object.defineProperty(nonEnumerable, "runAt", { enumerable: false, value: "2028-02-29T12:00:00Z" });
const symbol = { [Symbol("runAt")]: "2028-02-29T12:00:00Z" };
const cases = [
  ["valid leap instant", { runAt: "2028-02-29T12:00:00.1Z" }, "QUEUE_DATABASE_URL_REQUIRED"],
  ["genuine Date", { runAt: new Date("2028-02-29T12:00:00Z") }, "QUEUE_DATABASE_URL_REQUIRED"],
  ["accessor", accessor, "QUEUE_OPTIONS_INVALID"],
  ["non-enumerable", nonEnumerable, "QUEUE_OPTIONS_INVALID"],
  ["symbol", symbol, "QUEUE_OPTIONS_INVALID"],
  ["impossible day", { runAt: "2026-02-31T12:00:00Z" }, "QUEUE_RUN_AT_INVALID"],
  ["four fractional digits", { runAt: "2026-08-30T12:00:00.1234Z" }, "QUEUE_RUN_AT_INVALID"],
  ["timezone offset", { runAt: "2026-08-30T12:00:00+00:00" }, "QUEUE_RUN_AT_INVALID"],
];
const failures = [];
for (const [label, options, expected] of cases) {
  const observed = await code(options);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (reads !== 0) failures.push(`runAt getter executed ${reads} time(s)`);
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(&node, "replay the strict descriptor-safe runAt boundary");
}

#[test]
fn qa_round3_prior_recursive_named_result_blocker_stays_fixed_across_both_branches() {
    let fixture = Fixture::new("recursive-result-replay");
    fixture.write(
        "server/types.nox",
        "type Receipt { id: String }\ntype Failure { reason: String }\n",
    );
    fixture.write(
        "server/queues/Outcome.nox",
        r#"import { Receipt, Failure } from "../types.nox"
queue Outcome {
    payload { result: Optional<Result<Receipt, Failure>> }
    retry: 2
    backoff: 2s
}
"#,
    );
    assert_success(&fixture.build(), "build recursive named Result replay");

    let node = fixture.run_node(
        "recursive-result-replay",
        r#"import { enqueue } from "./server/handler.js";
async function code(result) {
  try { await enqueue("Outcome", { result }); } catch (error) { return error?.code ?? error?.name; }
  return null;
}
const cases = [
  ["none", null, "QUEUE_DATABASE_URL_REQUIRED"],
  ["ok", { tag: "Ok", value: { id: "r-1" } }, "QUEUE_DATABASE_URL_REQUIRED"],
  ["err", { tag: "Err", value: { reason: "closed" } }, "QUEUE_DATABASE_URL_REQUIRED"],
  ["bad ok", { tag: "Ok", value: { id: 1 } }, "QUEUE_PAYLOAD_TYPE"],
  ["bad err", { tag: "Err", value: { reason: false } }, "QUEUE_PAYLOAD_TYPE"],
];
const failures = [];
for (const [label, result, expected] of cases) {
  const observed = await code(result);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(&node, "replay recursive named Result validation");
}

#[test]
fn qa_round3_queue_policy_edits_invalidate_and_content_touches_hit_cache() {
    let fixture = Fixture::new("policy-cache");
    fixture.write(
        "server/queues/Policy.nox",
        "queue Policy { payload { label: String } retry: 1 backoff: 1s }\n",
    );
    assert_success(&fixture.build(), "build initial queue policy");

    fixture.write(
        "server/queues/Policy.nox",
        "queue Policy { payload { label: String } retry: 4 backoff: 7s }\n",
    );
    let edited = fixture.build();
    assert_success(&edited, "rebuild edited queue policy");
    assert!(
        String::from_utf8_lossy(&edited.stdout).contains("persistent cache: miss"),
        "queue policy edit reused stale cache:\n{}",
        output_text(&edited)
    );
    let manifest = fs::read_to_string(fixture.root.join("dist/server/queues.manifest.json"))
        .expect("read edited queue manifest");
    assert!(
        manifest.contains("\"retry\":4") && manifest.contains("\"backoffMs\":7000"),
        "edited queue policy did not reach emitted manifest: {manifest}"
    );

    let identical = fs::read_to_string(fixture.root.join("server/queues/Policy.nox"))
        .expect("read queue policy for content-identical touch");
    fixture.write("server/queues/Policy.nox", &identical);
    let touched = fixture.build();
    assert_success(&touched, "rebuild content-identical queue policy");
    assert!(
        String::from_utf8_lossy(&touched.stdout).contains("persistent cache: hit"),
        "content-identical queue touch missed cache:\n{}",
        output_text(&touched)
    );
}