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);
fn scratch_root(tag: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock after epoch")
.as_nanos();
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"noxid-wo45-phase2-{tag}-{}-{nonce}-{n}",
std::process::id()
));
fs::create_dir_all(&root).expect("create WO-45 phase-2 scratch root");
root
}
fn write(path: &Path, contents: &str) {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create parent directory");
}
fs::write(path, contents).expect("write project file");
}
fn combined(output: &Output) -> String {
format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
}
fn build(root: &Path) -> Output {
Command::new(env!("CARGO_BIN_EXE_noxid"))
.args([
"build",
root.to_str().expect("utf-8 project root"),
"--out-dir",
root.join("dist").to_str().expect("utf-8 out dir"),
])
.output()
.expect("run noxid build")
}
const HOST: &str = "export default { async fetch(request: Request): Promise<Response> { return new Response(\"ok\"); } }\n";
#[test]
fn imported_distinct_struct_fields_generate_result_position_validators() {
let root = scratch_root("imported-result");
write(&root.join("Noxid.toml"), "[app]\ntitle = \"Imported\"\n");
write(
&root.join("src/types/progress.nox"),
"type ConceptId = distinct String\n\
type Weight = distinct Int\n\
type ProgressSnapshot {\n concepts: Array<ConceptId>\n weight: Optional<Weight>\n}\n",
);
write(
&root.join("server/api/progress.get.nox"),
"import { ProgressSnapshot } from \"../../src/types/progress.nox\"\n\n\
endpoint Progress {\n version: 1\n result: ProgressSnapshot\n}\n",
);
write(&root.join("server/host.ts"), HOST);
let output = build(&root);
assert!(
output.status.success(),
"imported distinct fields in a result position must build:\n{}",
combined(&output)
);
let validators =
fs::read_to_string(root.join("dist/server/validators.js")).expect("emitted validators.js");
for expected in [
"function $validate_ValidatorConceptId(",
"function $validate_ValidatorWeight(",
"validateTypeProgressSnapshot",
] {
assert!(
validators.contains(expected),
"generated validators omitted `{expected}`:\n{validators}"
);
}
assert!(
validators.contains("(item, itemPath) => $string(item, resource, itemPath)"),
"Array<ConceptId> is not validated element-wise as String:\n{validators}"
);
assert!(
validators.contains("$int("),
"Optional<Weight> is not validated as Int:\n{validators}"
);
let contract =
fs::read_to_string(root.join("dist/api-contract.json")).expect("emitted api-contract.json");
assert!(
contract.contains("\"type\":\"Array<ConceptId>\""),
"the API contract erased the imported distinct to its base:\n{contract}"
);
}
#[test]
fn compiler_owned_server_handlers_erase_distinct_construction_and_unwrap() {
let root = scratch_root("server-erasure");
write(&root.join("Noxid.toml"), "[app]\ntitle = \"Erasure\"\n");
write(
&root.join("src/routes/+page.nox"),
"component Home { view { <main>Home</main> } }\n",
);
write(
&root.join("server/api/echo.get.nox"),
"type UserId = distinct String\n\
endpoint Echo {\n version: 1\n query { id: UserId }\n result: String\n handler { return id.base() }\n}\n",
);
write(
&root.join("server/api/mint.get.nox"),
"type TicketId = distinct String\n\
endpoint Mint {\n version: 1\n result: TicketId\n handler { return TicketId(\"t-1\") }\n}\n",
);
write(&root.join("server/host.ts"), HOST);
let output = build(&root);
assert!(
output.status.success(),
"compiler-owned server handlers must lower distinct construction and \
unwrap:\n{}",
combined(&output)
);
let handler =
fs::read_to_string(root.join("dist/server/handler.js")).expect("emitted handler.js");
assert!(
handler.contains("return \"t-1\";"),
"construction did not erase to its base literal:\n{handler}"
);
assert!(
!handler.contains("TicketId(") && !handler.contains("distinct-"),
"a distinct wrapper or synthetic id leaked into the server handler:\n{handler}"
);
}