Files
core/test/rust/native/application/mod.rs
T
2026-11-03 03:32:07 +00:00

260 lines
8.4 KiB
Rust

use super::{Directory, fixture};
mod auth;
mod database;
mod http;
mod job;
mod run;
mod tenant;
mod transport;
mod upload;
use dever::compiler::{build, project::Project};
use std::{
path::Path,
process::{Command, Output},
};
fn execute(binary: &Path, name: &str, input: &str) -> Output {
Command::new(binary).args([name, input]).output().unwrap()
}
#[test]
fn application_commands_execute_with_strict_typed_json_and_original_faults() {
let directory = Directory::new();
let project = Project::load(&fixture().with_file_name("application")).unwrap();
let program = project.check().unwrap();
assert_eq!(program.commands.len(), 9);
let binary =
build::build(&program, &project.sources, "main", &directory.0.join("app")).unwrap();
for (name, input, data) in [
("greet", r#"{"name":"雪"}"#, r#""[Hello, 雪]""#),
("double", r#"{"value":21}"#, "42"),
("pair", r#"{"value":41}"#, r#"{"first":41,"second":42}"#),
("empty", "{}", "null"),
("positive", r#"{"value":7}"#, "7"),
("divide", r#"{"value":6,"divisor":2}"#, "3.0"),
("maybe", "{}", "null"),
("maybe", r#"{"value":null}"#, "null"),
("maybe", r#"{"value":7}"#, "7"),
(
"maybe",
r#"{"value":-9223372036854775808}"#,
"-9223372036854775808",
),
(
"maybe",
r#"{"value":9223372036854775807}"#,
"9223372036854775807",
),
(
"echo",
r#"{"value":{"id":"a","name":"雪","scores":[1,2]}}"#,
r#"{"id":"a","name":"雪","scores":[1,2],"ratio":null}"#,
),
(
"many",
r#"{"rows":[{"id":"a","name":"雪","scores":[],"ratio":0.5}]}"#,
r#"[{"id":"a","name":"雪","scores":[],"ratio":0.5}]"#,
),
] {
let result = execute(&binary, &format!("demo.greeting.{name}"), input);
assert!(
result.status.success(),
"{name}: {}",
String::from_utf8_lossy(&result.stderr)
);
assert_eq!(
String::from_utf8(result.stdout).unwrap(),
format!("{{\"code\":0,\"message\":\"ok\",\"data\":{data}}}\n")
);
}
let name = "雪".repeat(24000);
let input = format!(r#"{{"rows":[{{"id":"a","name":"{name}","scores":[1,2]}}]}}"#);
let result = execute(&binary, "demo.greeting.many", &input);
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
assert_eq!(
String::from_utf8(result.stdout).unwrap(),
format!(
"{{\"code\":0,\"message\":\"ok\",\"data\":[{{\"id\":\"a\",\"name\":\"{name}\",\"scores\":[1,2],\"ratio\":null}}]}}\n"
)
);
let input =
format!(r#"{{"rows":[{{"id":"a","name":"{name}","na\u006de":"duplicate","scores":[]}}]}}"#);
let result = execute(&binary, "demo.greeting.many", &input);
assert!(!result.status.success());
assert!(result.stdout.is_empty());
for (name, input) in [
("double", "{}"),
("double", r#"{"value":1,"value":2}"#),
("double", r#"{"value":1,"extra":2}"#),
("double", "[1]"),
("double", r#"{"value":1.5}"#),
("double", r#"{"value":9223372036854775808}"#),
("double", r#"{"value":"1"}"#),
("double", r#"{"value":null}"#),
("maybe", r#"{"value":"7"}"#),
("maybe", r#"{"value":null,"value":7}"#),
("double", r#"{"value":1} {}"#),
("echo", r#"{"value":["a","b",[],null]}"#),
("echo", r#"{"value":{"id":1,"name":"a","scores":[]}}"#),
(
"echo",
r#"{"value":{"id":"a","name":"b","name":"c","scores":[]}}"#,
),
(
"echo",
r#"{"value":{"id":"a","name":"b","scores":[],"extra":0}}"#,
),
("many", r#"{"rows":[["a","b",[],null]]}"#),
(
"many",
r#"{"rows":[{"id":"a","name":"b","scores":[],"na\u006de":"c"}]}"#,
),
(
"many",
r#"{"rows":[{"id":"a","name":"b","scores":[],"ratio":null,"ratio":0.5}]}"#,
),
(
"many",
r#"{"rows":[{"id":"a","name":"b","scores":[],"extra":{}}]}"#,
),
("empty", "[]"),
("divide", r#"{"value":1e400,"divisor":2}"#),
] {
let result = execute(&binary, &format!("demo.greeting.{name}"), input);
assert!(!result.status.success(), "accepted {name}: {input}");
assert!(result.stdout.is_empty());
assert!(
String::from_utf8_lossy(&result.stderr).contains("invalid command input"),
"{name} {input}: {}",
String::from_utf8_lossy(&result.stderr)
);
}
for (name, input, message) in [
(
"double",
r#"{"value":9223372036854775807}"#,
"integer arithmetic fault",
),
("positive", r#"{"value":-1}"#, "Problem.Rejected"),
] {
let result = execute(&binary, &format!("demo.greeting.{name}"), input);
assert!(!result.status.success());
assert!(result.stdout.is_empty());
let error = String::from_utf8(result.stderr).unwrap();
assert!(
error.contains("domain.dever:") && error.contains(message),
"{error}"
);
}
let result = execute(
&binary,
"demo.greeting.divide",
r#"{"value":1,"divisor":0}"#,
);
assert!(!result.status.success());
assert!(result.stdout.is_empty());
assert!(String::from_utf8_lossy(&result.stderr).contains("non-finite Float"));
for arguments in [
vec![],
vec!["demo.greeting.empty"],
vec!["missing", "{}"],
vec!["demo.greeting.empty", "{}", "extra"],
] {
let result = Command::new(&binary).args(arguments).output().unwrap();
assert!(!result.status.success());
assert!(result.stdout.is_empty());
}
let rejected = directory.0.join("bypass");
assert!(
build::build(
&program,
&project.sources,
"demo.utility.decorate",
&rejected
)
.is_err()
);
assert!(!rejected.exists());
}
fn source(root: &Path, relative: &str) {
let path = root.join(relative);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, "layout validation precedes parsing").unwrap();
}
#[test]
fn application_layout_checks_roles_topics_and_project_root() {
for (files, needle) in [
(
vec!["module/shop/account/main.dever"],
"expected component/domain/role",
),
(
vec!["module/shop/account/test.dever"],
"role 'test' is not implemented yet",
),
(
vec![
"module/shop/account/app.dever",
"module/shop/account/app/read.dever",
],
"not both",
),
(
vec!["module/shop/account/domain/rules/read.dever"],
"must be flat",
),
(
vec!["module/shop/account/app/utils.dever"],
"generic application",
),
] {
let directory = Directory::new();
for path in files {
source(&directory.0, path);
}
let error = Project::load(&directory.0)
.err()
.expect("invalid application layout accepted");
assert!(error.contains(needle), "{error}");
}
let directory = Directory::new();
for path in [
"module/shop/account/app/read.dever",
"module/shop/account/app/write.dever",
"module/shop/account/api.dever",
"module/shop/account/job.dever",
"module/shop/account/api/admin/read.dever",
"module/shop/account/model.dever",
"module/shop/account/model/session.dever",
] {
source(&directory.0, path);
}
assert!(Project::load(&directory.0).is_ok());
for relative in [
"module",
"module/shop",
"module/shop/account/app",
"module/shop/account/app/read.dever",
] {
let error = Project::load(&directory.0.join(relative))
.err()
.expect("project root bypass accepted");
assert!(error.contains("project root"), "{error}");
}
let linked = Directory::new();
std::os::unix::fs::symlink(directory.0.join("module"), linked.0.join("module")).unwrap();
assert!(
Project::load(&linked.0)
.err()
.unwrap()
.contains("not a link")
);
}