use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
fn repository() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.canonicalize()
.expect("canonical repository root")
}
struct Fixture {
root: PathBuf,
}
impl Fixture {
fn new(label: &str) -> Self {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock after Unix epoch")
.as_nanos();
let ordinal = NEXT_FIXTURE.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"noxid-wo54-{label}-{}-{ordinal}-{nonce}",
std::process::id()
));
fs::create_dir_all(&root).expect("create fixture root");
Self { root }
}
fn noxid(&self, directory: &Path, arguments: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(arguments)
.current_dir(directory)
.env_remove("DATABASE_URL")
.env_remove("SESSION_SECRET")
.output()
.expect("run noxid")
}
fn scaffold_app(&self, name: &str) -> PathBuf {
let output = self.noxid(&self.root, &["new", name, "--template", "app"]);
assert_success(&output, "scaffold the app template");
self.root.join(name)
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}
fn 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} failed:\n{}",
text(output)
);
}
fn node_binary() -> PathBuf {
for candidate in [
PathBuf::from("/opt/homebrew/opt/node@22/bin/node"),
PathBuf::from("node"),
] {
let available = Command::new(&candidate)
.args([
"--input-type=module",
"--eval",
"await import('node:sqlite')",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
if matches!(available, Ok(status) if status.success()) {
return candidate;
}
}
panic!("WO-54 requires Node.js 22 with the node:sqlite synchronous API");
}
fn install_pinned_packages(project: &Path) -> &'static str {
let installed = Command::new("pnpm")
.args(["install", "--frozen-lockfile", "--prefer-offline"])
.current_dir(project)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
if matches!(installed, Ok(status) if status.success()) {
assert!(
project.join("node_modules/drizzle-orm").exists(),
"pnpm install --frozen-lockfile did not install the pinned adapter dependency"
);
return "pnpm install --frozen-lockfile";
}
let packages = repository().join("node_modules");
assert!(
packages.join("drizzle-orm").exists() && packages.join("postgres").exists(),
"neither pnpm nor this repository's node_modules can provide the pinned packages"
);
#[cfg(unix)]
std::os::unix::fs::symlink(&packages, project.join("node_modules"))
.expect("link the repository's installed packages into the scaffold");
#[cfg(windows)]
std::os::windows::fs::symlink_dir(&packages, project.join("node_modules"))
.expect("link the repository's installed packages into the scaffold");
"vendored from this repository's node_modules"
}
fn run_node(node: &Path, directory: &Path, source: &str) -> Output {
Command::new(node)
.args(["--input-type=module", "--eval", source])
.current_dir(directory)
.output()
.expect("run the Node probe")
}
#[test]
fn scaffolding_vendors_the_vetted_plugin_files_byte_identically_with_a_hash_ledger() {
let fixture = Fixture::new("vendor");
let project = fixture.scaffold_app("demo");
let repository = repository();
let vendored = [
"plugins/drizzle-orm/VETTING.md",
"plugins/drizzle-orm/adapter.js",
"plugins/drizzle-orm/data-scopes.test.mjs",
"plugins/postgres/VETTING.md",
"tools/database-url.mjs",
"tools/node-sqlite.mjs",
];
let ledger = fs::read_to_string(project.join(".noxid-plugins.json")).expect("read the ledger");
for relative in vendored {
let scaffolded = fs::read(project.join(relative))
.unwrap_or_else(|error| panic!("read scaffolded {relative}: {error}"));
let original = fs::read(repository.join(relative))
.unwrap_or_else(|error| panic!("read repository {relative}: {error}"));
assert_eq!(
scaffolded, original,
"{relative} was not vendored byte-identically"
);
assert!(
ledger.contains(&format!("\"path\": \"{relative}\"")),
"{relative} is vendored but absent from the ledger:\n{ledger}"
);
}
for (record, package) in [
("plugins/drizzle-orm/VETTING.md", "drizzle-orm"),
("plugins/postgres/VETTING.md", "postgres"),
] {
let contents = fs::read_to_string(repository.join(record)).expect("read vetting record");
let version = contents
.lines()
.find_map(|line| line.trim().strip_prefix("version:"))
.expect("vetting record version")
.trim();
assert!(
ledger.contains(&format!(
"\"package\": \"{package}\", \"version\": \"{version}\""
)),
"the ledger does not pin {package} at the vetted {version}:\n{ledger}"
);
let manifest =
fs::read_to_string(project.join("package.json")).expect("read scaffold package.json");
assert!(
manifest.contains(&format!("\"{package}\": \"{version}\"")),
"package.json does not pin {package} at the vetted {version}:\n{manifest}"
);
}
assert!(
!project.join("node_modules").exists(),
"`noxid new` installed packages; the first install is the developer's"
);
assert_eq!(
fs::read_to_string(project.join("pnpm-lock.yaml")).expect("scaffold lockfile"),
fs::read_to_string(repository.join("examples/templates/app/pnpm-lock.yaml"))
.expect("template lockfile"),
"the scaffolded lockfile is not the committed template lockfile"
);
}
#[test]
fn the_scaffolded_endpoint_and_action_round_trip_real_sqlite_through_the_vendored_adapter() {
let fixture = Fixture::new("round-trip");
let project = fixture.scaffold_app("demo");
let provisioning = install_pinned_packages(&project);
let node = node_binary();
let database = project.join("app.db");
let database_url = format!("sqlite://{}", database.display());
let migrated = Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(["db", "migrate"])
.current_dir(&project)
.env("DATABASE_URL", &database_url)
.output()
.expect("migrate the scaffolded database");
assert_success(&migrated, "migrate the scaffolded database");
assert_success(
&fixture.noxid(&project, &["build", ".", "--out-dir", "dist"]),
"build the scaffolded project",
);
assert_success(
&fixture.noxid(
&project,
&["adapt", ".", "--adapter", "node", "--out-dir", "dist"],
),
"adapt the scaffolded project for Node",
);
let probe = format!(
r#"
process.env.DATABASE_URL = {database_url:?};
process.env.SESSION_SECRET = "wo54-round-trip-secret";
const {{ fetch: handler }} = await import("./dist/server/handler.js");
const origin = "http://127.0.0.1:9999";
const call = (path, init) => handler(new Request(origin + path, init), process.env, {{ waitUntil() {{}} }});
// Round 4: the scaffold is secure by default. The unauthenticated refusal is
// asserted first, then the round trip runs under the session the shipped
// sign-in door mints.
const unauthenticated = await call("/api/notes/welcome");
console.log(`unauth-status=${{unauthenticated.status}}`);
console.log(`unauth-code=${{(await unauthenticated.json())?.error?.code ?? ""}}`);
const signIn = await call("/_noxid/actions/action%3AAppPage.signIn", {{
method: "POST",
headers: {{ "content-type": "application/json", "x-noxid-route-id": "route:/" }},
body: JSON.stringify({{ arguments: {{ request: {{ displayName: "wo54trip" }} }} }}),
}});
const signInBody = await signIn.json();
const cookie = ((signIn.headers.getSetCookie?.() ?? [])[0] ?? "").split(";")[0];
console.log(`signin-status=${{signIn.status}}`);
console.log(`signin-principal=${{signInBody?.value?.userId ?? ""}}`);
console.log(`signin-cookie=${{cookie.startsWith("noxid_session=")}}`);
const before = await call("/api/notes/welcome", {{ headers: {{ cookie }} }});
const beforeType = before.headers.get("content-type") ?? "";
const beforeBody = await before.text();
console.log(`before-status=${{before.status}}`);
console.log(`before-json=${{beforeType.startsWith("application/json")}}`);
console.log(`before-empty=${{beforeBody.includes("\"notes\":[]")}}`);
const action = await call("/_noxid/actions/action%3AAppPage.createNote", {{
method: "POST",
headers: {{ "content-type": "application/json", "x-noxid-route-id": "route:/", cookie }},
body: JSON.stringify({{ arguments: {{ request: {{ boardId: "welcome", title: "Written through SQLite" }} }} }}),
}});
const actionBody = await action.text();
console.log(`action-status=${{action.status}}`);
console.log(`action-created=${{actionBody.includes("Written through SQLite")}}`);
const after = await call("/api/notes/welcome", {{ headers: {{ cookie }} }});
const afterBody = await after.text();
console.log(`after-status=${{after.status}}`);
console.log(`after-read-write=${{afterBody.includes("Written through SQLite")}}`);
const {{ DatabaseSync }} = await import("node:sqlite");
const database = new DatabaseSync({database_url_path:?}, {{ readOnly: true }});
const rows = database.prepare("SELECT id, owner_id, board_id, title, done FROM notes ORDER BY id").all();
database.close();
console.log(`sqlite-rows=${{JSON.stringify(rows)}}`);
"#,
database_url = database_url,
database_url_path = database.display().to_string(),
);
let requests = run_node(&node, &project, &probe);
let evidence = text(&requests);
assert_success(&requests, "call the endpoint through the shipped handler");
for expected in [
"unauth-status=403",
"unauth-code=SESSION_PRINCIPAL_REQUIRED",
"signin-status=200",
"signin-principal=wo54trip",
"signin-cookie=true",
"before-status=200",
"before-json=true",
"before-empty=true",
"action-status=200",
"action-created=true",
"after-status=200",
"after-read-write=true",
] {
assert!(
evidence.contains(expected),
"the scaffolded full-stack round trip is missing `{expected}` (packages: \
{provisioning}):\n{evidence}"
);
}
assert!(
evidence.contains("\"owner_id\":\"wo54trip\"")
&& evidence.contains("\"title\":\"Written through SQLite\""),
"the scoped SQLite table does not hold the principal-bound row the action wrote \
(packages: {provisioning}):\n{evidence}"
);
}
#[test]
fn editing_a_vendored_plugin_file_refuses_the_build() {
let fixture = Fixture::new("vendor-drift");
let project = fixture.scaffold_app("demo");
let adapter = project.join("plugins/drizzle-orm/adapter.js");
let original = fs::read_to_string(&adapter).expect("read the vendored adapter");
fs::write(&adapter, format!("{original}\n// a local edit\n")).expect("edit the adapter");
let built = fixture.noxid(&project, &["build", ".", "--out-dir", "dist"]);
let evidence = text(&built);
assert!(
!built.status.success() && evidence.contains("error[PLUGIN_VENDOR_DRIFT]"),
"an edited vendored adapter did not refuse the build:\n{evidence}"
);
assert!(
!project.join("dist").exists(),
"the refused build still emitted output"
);
}
#[test]
fn a_lockfile_that_disagrees_with_the_pinned_vetted_version_refuses_the_build() {
let fixture = Fixture::new("lock-drift");
let project = fixture.scaffold_app("demo");
let lockfile = project.join("pnpm-lock.yaml");
let locked = fs::read_to_string(&lockfile).expect("read the scaffold lockfile");
assert!(locked.contains("drizzle-orm@0.45.2:"), "{locked}");
fs::write(
&lockfile,
locked.replace("drizzle-orm@0.45.2:", "drizzle-orm@0.45.1:"),
)
.expect("drift the lockfile");
let built = fixture.noxid(&project, &["build", ".", "--out-dir", "dist"]);
let evidence = text(&built);
assert!(
!built.status.success() && evidence.contains("error[NPM_IMPORT_UNVETTED]"),
"a lockfile that disagrees with the vetted pin did not refuse the build:\n{evidence}"
);
assert!(
evidence.contains("0.45.1") && evidence.contains("0.45.2"),
"the refusal did not name both the locked and the vetted version:\n{evidence}"
);
}
#[test]
fn a_missing_lockfile_entry_for_a_pinned_package_refuses_the_build() {
let fixture = Fixture::new("lock-missing");
let project = fixture.scaffold_app("demo");
fs::remove_file(project.join("pnpm-lock.yaml")).expect("remove the scaffold lockfile");
let built = fixture.noxid(&project, &["build", ".", "--out-dir", "dist"]);
let evidence = text(&built);
assert!(
!built.status.success() && evidence.contains("error[NPM_IMPORT_UNVETTED]"),
"a project with no lockfile for its pinned vetted packages still built:\n{evidence}"
);
}
#[test]
fn the_vendored_data_scope_test_runs_inside_the_scaffold() {
let fixture = Fixture::new("data-scopes");
let project = fixture.scaffold_app("demo");
let provisioning = install_pinned_packages(&project);
let node = node_binary();
let output = Command::new(&node)
.args(["--test", "plugins/drizzle-orm/data-scopes.test.mjs"])
.current_dir(&project)
.output()
.expect("run the vendored data-scopes test");
assert!(
output.status.success(),
"the vendored scope-boundary proof does not run in a scaffolded project (packages: \
{provisioning}):\n{}",
text(&output)
);
}
#[test]
fn vet_reports_vendored_drift_and_refuses_until_sync_restores_the_build() {
let fixture = Fixture::new("vet-drift");
let project = fixture.scaffold_app("demo");
let adapter = project.join("plugins/drizzle-orm/adapter.js");
let vetted = fs::read(&adapter).expect("read the vendored adapter");
let ledger = fs::read(project.join(".noxid-plugins.json")).expect("read the ledger");
let mut tampered = vetted.clone();
let last = tampered.len() - 1;
tampered[last] ^= 1;
fs::write(&adapter, &tampered).expect("tamper with one vendored byte");
let reported = fixture.noxid(&project, &["vet"]);
let report = text(&reported);
assert!(
!reported.status.success(),
"`noxid vet` accepted a drifted vendored file:\n{report}"
);
assert!(
report.contains("PLUGIN_VENDOR_DRIFT")
&& report.contains("drifted plugins/drizzle-orm/adapter.js")
&& report.contains("current tools/node-sqlite.mjs"),
"`noxid vet` did not name the drifted file in a per-file report:\n{report}"
);
assert_eq!(
fs::read(&adapter).expect("read the adapter after a refusal"),
tampered,
"`noxid vet` without --sync rewrote a file"
);
let synced = fixture.noxid(&project, &["vet", "--sync"]);
assert_success(&synced, "sync the drifted vendored file");
assert!(
text(&synced).contains("synced plugins/drizzle-orm/adapter.js"),
"`--sync` rewrote a file without saying which:\n{}",
text(&synced)
);
assert_eq!(
fs::read(&adapter).expect("read the restored adapter"),
vetted,
"`--sync` did not restore the embedded copy byte for byte"
);
assert_eq!(
fs::read(project.join(".noxid-plugins.json")).expect("read the ledger"),
ledger,
"`--sync` changed a ledger that already matched this compiler"
);
assert_success(
&fixture.noxid(&project, &["build", ".", "--out-dir", "dist"]),
"build the synced project",
);
}
#[test]
fn an_older_scaffold_syncs_its_vendored_files_and_ledger_to_this_compiler() {
let fixture = Fixture::new("vet-older-cli");
let current = fixture.scaffold_app("current");
let older = fixture.root.join("older");
let scaffolded = Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(["new", "older", "--template", "app"])
.current_dir(&fixture.root)
.env(
"NOXID_TEST_OLDER_VENDORED_PLUGIN",
"plugins/drizzle-orm/adapter.js",
)
.output()
.expect("scaffold with the older-compiler hook");
assert_success(&scaffolded, "scaffold a project from an older compiler");
let adapter = older.join("plugins/drizzle-orm/adapter.js");
let ledger = older.join(".noxid-plugins.json");
assert_ne!(
fs::read(&adapter).expect("read the older adapter"),
fs::read(current.join("plugins/drizzle-orm/adapter.js")).expect("read this adapter"),
"the older-compiler hook produced the current vendored bytes"
);
assert_success(
&fixture.noxid(&older, &["build", ".", "--out-dir", "dist"]),
"build the older scaffold, whose ledger matches its own files",
);
let reported = fixture.noxid(&older, &["vet"]);
assert!(
!reported.status.success() && text(&reported).contains("PLUGIN_VENDOR_DRIFT"),
"`noxid vet` did not notice an older scaffold:\n{}",
text(&reported)
);
let synced = fixture.noxid(&older, &["vet", "--sync"]);
assert_success(&synced, "sync an older scaffold");
let synced_text = text(&synced);
assert!(
synced_text.contains("synced plugins/drizzle-orm/adapter.js")
&& synced_text.contains("synced .noxid-plugins.json"),
"`--sync` did not report both the file and the ledger:\n{synced_text}"
);
assert_eq!(
fs::read(&adapter).expect("read the synced adapter"),
fs::read(current.join("plugins/drizzle-orm/adapter.js")).expect("read this adapter"),
"`--sync` did not bring the vendored file up to this compiler"
);
assert_eq!(
fs::read_to_string(&ledger).expect("read the synced ledger"),
fs::read_to_string(current.join(".noxid-plugins.json")).expect("read this ledger"),
"`--sync` did not update the ledger's hashes and source commit"
);
assert_success(
&fixture.noxid(&older, &["build", ".", "--out-dir", "synced-dist"]),
"build the synced older scaffold",
);
}
#[test]
fn vet_sync_on_a_current_scaffold_changes_nothing_and_says_so() {
let fixture = Fixture::new("vet-noop");
let project = fixture.scaffold_app("demo");
let before = fs::read(project.join("plugins/drizzle-orm/adapter.js")).expect("read adapter");
let ledger = fs::read(project.join(".noxid-plugins.json")).expect("read ledger");
for arguments in [&["vet"][..], &["vet", "--sync"][..]] {
let output = fixture.noxid(&project, arguments);
assert_success(&output, "check a current scaffold");
let report = text(&output);
assert!(
report.contains("nothing to sync") && !report.contains("synced "),
"`noxid {}` on a current scaffold did not report a no-op:\n{report}",
arguments.join(" ")
);
assert_eq!(
fs::read(project.join("plugins/drizzle-orm/adapter.js")).expect("read adapter"),
before,
"a no-op rewrote a vendored file"
);
assert_eq!(
fs::read(project.join(".noxid-plugins.json")).expect("read ledger"),
ledger,
"a no-op rewrote the ledger"
);
}
}
#[test]
fn vet_outside_a_scaffolded_project_explains_itself() {
let fixture = Fixture::new("vet-no-ledger");
let refused = fixture.noxid(&fixture.root, &["vet"]);
let report = text(&refused);
assert!(
!refused.status.success()
&& report.contains("PLUGIN_LEDGER_MISSING")
&& report.contains("noxid new --template app")
&& !report.contains("npm-vet.mjs"),
"`noxid vet` outside a scaffolded project did not explain itself:\n{report}"
);
}
#[test]
fn a_missing_ledger_beside_vendored_plugin_files_refuses_the_build_and_names_the_remedy() {
let fixture = Fixture::new("ledger-deleted");
let project = fixture.scaffold_app("demo");
let adapter = project.join("plugins/drizzle-orm/adapter.js");
let vetted = fs::read(&adapter).expect("read the vendored adapter");
let ledger = project.join(".noxid-plugins.json");
assert!(ledger.is_file(), "the scaffold wrote no ledger");
fs::write(
&adapter,
format!(
"{}\n// tamper\nglobalThis.__TAMPER__ = 1;\n",
String::from_utf8_lossy(&vetted)
),
)
.expect("tamper with the vendored adapter");
fs::remove_file(&ledger).expect("delete the ledger");
let built = fixture.noxid(&project, &["build", ".", "--out-dir", "dist"]);
let evidence = text(&built);
assert!(
!built.status.success(),
"a tampered adapter with the ledger deleted still built:\n{evidence}"
);
assert!(
evidence.contains("error[PLUGIN_LEDGER_MISSING]")
&& evidence.contains("plugins/drizzle-orm/adapter.js"),
"the refusal does not name the missing ledger or the file it protects:\n{evidence}"
);
assert!(
evidence.contains("noxid vet --sync"),
"the refusal does not name a remedy the CLI can actually perform:\n{evidence}"
);
assert!(
!project.join("dist").exists(),
"the refused build still emitted output"
);
let synced = fixture.noxid(&project, &["vet", "--sync"]);
assert_success(&synced, "re-vendor the plugin files after a deleted ledger");
assert!(
ledger.is_file(),
"`noxid vet --sync` did not restore the ledger:\n{}",
text(&synced)
);
assert_eq!(
fs::read(&adapter).expect("read the restored adapter"),
vetted,
"`noxid vet --sync` did not restore the tampered adapter byte-identically"
);
let rechecked = fixture.noxid(&project, &["vet"]);
assert_success(&rechecked, "check the repaired scaffold");
assert!(
text(&rechecked).contains("nothing to sync"),
"the repaired scaffold still reports drift:\n{}",
text(&rechecked)
);
}
#[test]
fn a_project_that_vendors_no_plugins_needs_no_ledger() {
let fixture = Fixture::new("no-plugins");
let project = fixture.root.join("plain");
fs::create_dir_all(project.join("src/routes")).expect("create plain project");
fs::write(
project.join("Noxid.toml"),
"[app]\ntitle = \"No plugins\"\nroutes = \"src/routes\"\n",
)
.expect("write manifest");
fs::write(project.join("package.json"), "{\"type\":\"module\"}\n").expect("write package");
fs::write(
project.join("src/routes/+page.nox"),
"component Home { route { title: \"Home\" } view { <main>plain</main> } }\n",
)
.expect("write route");
assert!(
!project.join(".noxid-plugins.json").exists() && !project.join("plugins").exists(),
"the control project must vendor nothing"
);
let built = fixture.noxid(&project, &["build", ".", "--out-dir", "dist"]);
assert_success(&built, "build a project that vendors no plugins");
assert!(
!text(&built).contains("PLUGIN_LEDGER_MISSING"),
"a project with no vendored plugins was asked for a ledger:\n{}",
text(&built)
);
}