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-qa2-{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 2\"\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_round2_imported_named_result_branches_validate_through_deep_wrappers() {
let fixture = Fixture::new("deep-imports");
fixture.write(
"server/types.nox",
"type Receipt { id: Int }\ntype Failure { message: String }\n",
);
fixture.write(
"server/queues/Outcomes.nox",
r#"import { Receipt, Failure } from "../types.nox"
queue Outcomes {
payload { batches: Array<Map<String, Optional<Result<Receipt, Failure>>>> }
retry: 2
backoff: 2s
}
"#,
);
assert_success(&fixture.build(), "build deeply wrapped imported payload");
let node = fixture.run_node(
"deep-imports",
r#"import { enqueue } from "./server/handler.js";
async function code(operation) {
try { await operation(); } catch (error) { return error?.code; }
return null;
}
const valid = { batches: [{
accepted: { tag: "Ok", value: { id: 7 } },
refused: { tag: "Err", value: { message: "closed" } },
absent: null,
}] };
if (await code(() => enqueue("Outcomes", valid)) !== "QUEUE_DATABASE_URL_REQUIRED") throw new Error("valid named Result branches did not reach storage");
if (await code(() => enqueue("Outcomes", { batches: [{ bad: { tag: "Ok", value: { id: "7" } } }] })) !== "QUEUE_PAYLOAD_TYPE") throw new Error("invalid Receipt branch escaped");
if (await code(() => enqueue("Outcomes", { batches: [{ bad: { tag: "Err", value: { message: 7 } } }] })) !== "QUEUE_PAYLOAD_TYPE") throw new Error("invalid Failure branch escaped");
"#,
);
assert_success(&node, "execute deeply wrapped imported validation");
}
#[test]
fn qa_round2_imported_payload_edits_invalidate_but_content_touches_hit_cache() {
let fixture = Fixture::new("import-cache");
fixture.write("server/types.nox", "type Receipt { id: Int }\n");
fixture.write(
"server/queues/Outcomes.nox",
r#"import { Receipt } from "../types.nox"
queue Outcomes { payload { result: Result<Receipt, String> } retry: 1 backoff: 1s }
"#,
);
assert_success(&fixture.build(), "build initial imported queue payload");
fixture.write("server/types.nox", "type Receipt { id: String }\n");
let edited = fixture.build();
assert_success(&edited, "rebuild edited imported queue payload");
assert!(
String::from_utf8_lossy(&edited.stdout).contains("persistent cache: miss"),
"imported type edit reused stale queue cache:\n{}",
output_text(&edited)
);
let node = fixture.run_node(
"edited-validator",
r#"import { enqueue } from "./server/handler.js";
async function code(payload) {
try { await enqueue("Outcomes", payload); } catch (error) { return error?.code; }
return null;
}
if (await code({ result: { tag: "Ok", value: { id: 7 } } }) !== "QUEUE_PAYLOAD_TYPE") throw new Error("stale Int validator survived imported edit");
if (await code({ result: { tag: "Ok", value: { id: "seven" } } }) !== "QUEUE_DATABASE_URL_REQUIRED") throw new Error("fresh String validator was not emitted");
"#,
);
assert_success(&node, "execute refreshed queue validator");
let identical = fs::read_to_string(fixture.root.join("server/types.nox"))
.expect("read imported type for touch");
fixture.write("server/types.nox", &identical);
let touched = fixture.build();
assert_success(&touched, "rebuild content-identical imported queue payload");
assert!(
String::from_utf8_lossy(&touched.stdout).contains("persistent cache: hit"),
"content-identical touch missed queue cache:\n{}",
output_text(&touched)
);
}
#[test]
fn qa_round2_enqueue_options_are_ordinary_data_and_run_at_is_date_or_iso_only() {
let fixture = Fixture::new("hostile-options");
fixture.write(
"server/queues/Safe.nox",
"queue Safe { payload { label: String } retry: 0 backoff: 1s }\n",
);
assert_success(&fixture.build(), "build hostile options fixture");
let node = fixture.run_node(
"hostile-options",
r#"import { enqueue } from "./server/handler.js";
async function code(options) {
try { await enqueue("Safe", { label: "ok" }, options); } catch (error) { return error?.code; }
return null;
}
let reads = 0;
const accessor = {};
Object.defineProperty(accessor, "runAt", { enumerable: true, get() { reads += 1; return "2026-08-30T12:00:00Z"; } });
const inherited = Object.create({ runAt: "2026-08-30T12:00:00Z" });
const cases = [
["accessor", accessor, "QUEUE_OPTIONS_INVALID"],
["inherited", inherited, "QUEUE_OPTIONS_INVALID"],
["numeric", { runAt: 0 }, "QUEUE_RUN_AT_INVALID"],
["boolean", { runAt: true }, "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, "reject effectful or non-temporal enqueue options");
}
#[test]
fn qa_round2_stopping_worker_clears_scheduled_poll_after_boundary_failure() {
let fixture = Fixture::new("worker-disposal");
fixture.write(
"server/queues/Safe.nox",
"queue Safe { payload {} retry: 0 backoff: 1s }\n",
);
assert_success(&fixture.build(), "build worker disposal fixture");
let node = fixture.run_node(
"worker-disposal",
r#"import { startQueueWorker } from "./server/handler.js";
const timers = new Map();
const errors = [];
let nextTimer = 0;
const worker = startQueueWorker({
pollIntervalMs: 17,
setTimeout(callback, delay) { const id = ++nextTimer; timers.set(id, { callback, delay }); return id; },
clearTimeout(id) { timers.delete(id); },
onError(error) { errors.push(error?.code); },
});
await new Promise((resolve) => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
if (errors.length !== 1 || errors[0] !== "QUEUE_DATABASE_URL_REQUIRED") throw new Error(`unexpected worker boundary errors: ${JSON.stringify(errors)}`);
if (timers.size !== 1 || [...timers.values()][0].delay !== 17) throw new Error(`poll timer was not scheduled exactly once: ${JSON.stringify([...timers.values()].map(({ delay }) => delay))}`);
await worker.stop();
if (timers.size !== 0) throw new Error(`worker stop leaked ${timers.size} timer(s)`);
"#,
);
assert_success(&node, "dispose queue worker polling owner");
}