260 lines
8.4 KiB
Rust
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")
|
|
);
|
|
}
|