use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
struct Fixture {
root: PathBuf,
}
impl Fixture {
fn new(tag: &str) -> Self {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock after epoch")
.as_nanos();
let ordinal = COUNTER.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"noxid-wo45-phase3-{tag}-{}-{nonce}-{ordinal}",
std::process::id()
));
fs::create_dir_all(&root).expect("create WO-45 phase-3 fixture");
fs::write(
root.join("Noxid.toml"),
"[app]\ntitle = \"WO-45 phase 3\"\n\n[server]\nruntime = \"node\"\n",
)
.expect("write project config");
fs::write(root.join("package.json"), "{\"type\":\"module\"}\n")
.expect("write Node module marker");
let fixture = Self { root };
fixture.write(
"src/routes/+page.nox",
"component Page {\n view { <p>ok</p> }\n}\n",
);
fixture
}
fn path(&self, relative: &str) -> PathBuf {
self.root.join(relative)
}
fn write(&self, relative: &str, contents: &str) {
let path = self.path(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 install_drizzle_adapter(&self) {
let repository = repository_root();
for relative in [
"plugins/drizzle-orm/adapter.js",
"tools/database-url.mjs",
"tools/node-sqlite.mjs",
] {
let source = repository.join(relative);
if !source.is_file() {
continue;
}
let target = self.path(relative);
fs::create_dir_all(target.parent().expect("adapter parent"))
.expect("create adapter directory");
fs::copy(&source, &target).expect("copy adapter source");
}
}
fn noxid(&self, arguments: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(arguments)
.output()
.expect("run noxid CLI")
}
fn build(&self) -> Output {
self.noxid(&[
"build",
self.root.to_str().expect("UTF-8 fixture path"),
"--out-dir",
self.path("dist").to_str().expect("UTF-8 output path"),
])
}
fn impact(&self, target: &str) -> Output {
self.noxid(&[
"impact",
self.root.to_str().expect("UTF-8 fixture path"),
target,
])
}
fn read_dist(&self, relative: &str) -> String {
fs::read_to_string(self.path(&format!("dist/{relative}")))
.unwrap_or_else(|error| panic!("read dist/{relative}: {error}"))
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}
fn repository_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
}
fn output_text(output: &Output) -> String {
format!(
"{}{}",
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)
);
}
const SCOPED_SCHEMA: &str = r#"import { pgTable, text } from "drizzle-orm/pg-core";
import { scopedTable } from "../../plugins/drizzle-orm/adapter.js";
export const notes = scopedTable(pgTable("notes", {
userId: text("user_id").notNull(),
body: text("body").notNull(),
}), "user_id");
"#;
const SCOPED_HOST: &str = r#"import { notes } from "./utils/schema.js";
export const endpoints = Object.freeze({
"endpoint:SaveNote@1": async () => notes !== undefined,
});
"#;
fn scoped_fixture(tag: &str, note_body: &str) -> Fixture {
let fixture = Fixture::new(tag);
fixture.install_drizzle_adapter();
fixture.write("server/utils/schema.ts", SCOPED_SCHEMA);
fixture.write("server/host.ts", SCOPED_HOST);
fixture.write("server/api/notes.post.nox", note_body);
fixture
}
#[test]
fn a_scoped_column_declared_as_a_string_fails_the_build() {
let fixture = scoped_fixture(
"scoped-string",
"endpoint SaveNote {\n version: 1\n body {\n userId: String\n note: String\n }\n result: Boolean\n}\n",
);
let output = fixture.build();
assert!(
!output.status.success(),
"a String scoped column must fail the build:\n{}",
output_text(&output)
);
let text = output_text(&output);
assert!(
text.contains("error[SCOPED_COLUMN_REQUIRES_PRINCIPAL_ID]"),
"{text}"
);
assert!(text.contains("`userId: String`"), "{text}");
assert!(text.contains("scoped table `notes`"), "{text}");
assert!(text.contains("PrincipalId"), "{text}");
assert!(
!fixture.path("dist").exists(),
"the refusal must land before the first output-directory mutation"
);
}
#[test]
fn a_scoped_column_typed_principal_id_builds_and_reaches_the_manifest() {
let fixture = scoped_fixture(
"scoped-typed",
"endpoint SaveNote {\n version: 1\n body {\n userId: PrincipalId\n note: String\n }\n result: Boolean\n}\n",
);
assert_success(&fixture.build(), "build a PrincipalId-typed scoped column");
let manifest = fixture.read_dist("server/security.manifest.json");
assert!(
manifest.contains(
r#"{"table":"notes","policy":"scoped","principalColumn":"user_id","principalType":"PrincipalId"}"#
),
"the security manifest must record the typed column:\n{manifest}"
);
let openapi = fixture.read_dist("api.openapi.json");
assert!(openapi.contains("\"T_PrincipalId\""), "{openapi}");
assert!(
openapi.contains("\"x-noxid-distinct-base\":\"String\"")
|| openapi.contains("\"x-noxid-distinct-base\": \"String\""),
"{openapi}"
);
}
const WHOAMI_ENDPOINT: &str = r#"endpoint Whoami {
version: 1
result: String
handler {
#match context.principal {
System { return "system" }
User(user) { return user.id.base() }
Agent(actor) { return actor.id.base() }
}
}
}
"#;
#[test]
fn the_emitted_handler_erases_to_the_unchanged_runtime_principal_shape() {
let fixture = Fixture::new("erasure");
fixture.write("server/api/whoami.get.nox", WHOAMI_ENDPOINT);
assert_success(&fixture.build(), "build a principal-matching handler");
let handler = fixture.read_dist("server/handler.js");
assert!(
handler.contains("\"endpoint:Whoami@1\": async (args, context) =>"),
"the emitted handler must bind the context the dispatcher already passes"
);
for fragment in [
"= (context)[\"principal\"]",
".kind === \"system\"",
".kind === \"user\"",
".kind === \"agent\"",
"\"id\": __noxidPrincipal_endpoint_Whoami_0.scope",
"\"id\": __noxidPrincipal_endpoint_Whoami_0.agent",
"\"actingFor\": __noxidPrincipal_endpoint_Whoami_0.scope",
] {
assert!(
handler.contains(fragment),
"missing `{fragment}` in emitted handler:\n{handler}"
);
}
assert!(
handler.contains("function __noxidPrincipal(middlewareContext, environment, agent = null)"),
"the principal runtime must keep its existing shape"
);
}
#[test]
fn the_graph_lists_every_site_where_a_principal_leaves_its_type() {
let fixture = Fixture::new("graph");
fixture.write(
"server/host.ts",
"export const endpoints = Object.freeze({});\n",
);
fixture.write("server/api/whoami.get.nox", WHOAMI_ENDPOINT);
fixture.write(
"server/tasks/Audit.nox",
r#"task Audit {
schedule: "0 3 * * *"
handler {
#match context.principal {
System { let who = "system" }
User(user) { let who = user.id.base() }
Agent(actor) { let who = actor.id.base() }
}
}
}
"#,
);
fixture.write(
"server/queues/Reindex.nox",
r#"queue Reindex {
payload { key: String }
retry: 1
backoff: 1s
handler {
#match context.principal {
System { let who = "system" }
User(user) { let who = user.id.base() }
Agent(actor) { let who = actor.id.base() }
}
}
}
"#,
);
fixture.write(
"server/api/kind.get.nox",
r#"endpoint PrincipalKind {
version: 1
result: String
handler {
#match context.principal {
System { return "system" }
User(user) { return "user" }
Agent(actor) { return "agent" }
}
}
}
"#,
);
assert_success(&fixture.build(), "build the graph fixture");
let output = fixture.impact("distinct-unwrap:PrincipalId");
assert_success(&output, "query PrincipalId unwrap sites");
let report = output_text(&output);
for owner in [
"endpoint:Whoami@1",
"task:Audit",
"queue:Reindex",
"type:PrincipalId",
] {
assert!(
report.contains(owner),
"`noxid impact distinct-unwrap:PrincipalId` must list {owner}:\n{report}"
);
}
assert!(
!report.contains("endpoint:PrincipalKind@1"),
"a handler that never unwraps must not appear:\n{report}"
);
let agents = fixture.impact("distinct-unwrap:AgentId");
assert_success(&agents, "query AgentId unwrap sites");
let agents = output_text(&agents);
for owner in ["endpoint:Whoami@1", "task:Audit", "queue:Reindex"] {
assert!(agents.contains(owner), "{agents}");
}
}
#[test]
fn the_principal_union_is_in_every_project_graph() {
let fixture = Fixture::new("union");
assert_success(&fixture.build(), "build a project with no principal use");
let graph = fixture.read_dist("app.graph.json");
for node in [
"machine:compiler.Principal",
"variant:compiler.Principal.System",
"variant:compiler.Principal.User",
"variant:compiler.Principal.Agent",
"type:PrincipalId",
"type:AgentId",
] {
assert!(
graph.contains(node),
"the compiler-owned principal contract must be in every graph: {node}"
);
}
}
#[test]
fn the_website_learn_session_path_matches_on_the_principal() {
let website = repository_root().join("website");
let dogfood = fs::read_to_string(website.join("server/api/progress/learner.get.nox"))
.expect("read the website principal dogfood");
assert!(
dogfood.contains("#match context.principal"),
"the website dogfood must consume the principal:\n{dogfood}"
);
assert!(
dogfood.contains("User(user)") && dogfood.contains("user.id.base()"),
"the dogfood must match on User and unwrap explicitly:\n{dogfood}"
);
assert!(
dogfood.contains("learnSession"),
"the dogfood must sit on the Learn session path:\n{dogfood}"
);
let output_root = std::env::temp_dir().join(format!(
"noxid-wo45-phase3-website-{}-{}",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed)
));
let output = Command::new(env!("CARGO_BIN_EXE_noxid"))
.args([
"build",
website.to_str().expect("UTF-8 website path"),
"--out-dir",
])
.arg(&output_root)
.output()
.expect("build the website dogfood");
let emitted = fs::read_to_string(output_root.join("server/actions.js")).unwrap_or_default();
let _ = fs::remove_dir_all(&output_root);
assert_success(&output, "build the website with the principal dogfood");
assert!(
emitted.contains("\"endpoint:LearnerIdentity@1\": async (args, context) =>"),
"the website must emit the compiler-owned principal handler"
);
}