195 lines
6.6 KiB
Rust
195 lines
6.6 KiB
Rust
use super::{APP, PORT, packages, signature};
|
|
|
|
#[test]
|
|
fn external_worker_secrets_belong_only_in_settings() {
|
|
let adapter = "setting { token: Secret }\nexternal exec \"worker\" {}";
|
|
for kind in [
|
|
"Text",
|
|
"Secret",
|
|
"Secret?",
|
|
"List<Secret>",
|
|
"app.Credentials",
|
|
] {
|
|
let app = format!(
|
|
"type Problem {{ error Failed(message: Text) }}\ntype Credentials {{ tokens: List<Secret?> }}\nread(value: {kind}) (answer: Int) {{ answer = port.read(value) }}"
|
|
);
|
|
let port = format!("read(value: {kind}) (answer: Int) fails app.Problem");
|
|
let result = packages(&[
|
|
("shop.account.app", &app),
|
|
("shop.account.port", &port),
|
|
("shop.account.adapter", adapter),
|
|
]);
|
|
if kind == "Text" {
|
|
assert!(result.is_ok(), "{result:?}");
|
|
} else {
|
|
let error = result.unwrap_err();
|
|
assert!(
|
|
error.message.contains("Secret belongs only in setting"),
|
|
"{kind}: {error:?}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn settings_are_private_readonly_typed_values() {
|
|
let adapter = "setting { offset: Int\ntoken: Secret }\nport.read(value: Int) (answer: Int) { answer = helper(value) }\nhelper(value: Int) (answer: Int) pure { answer = value + setting.offset }";
|
|
let program = packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter", adapter),
|
|
])
|
|
.unwrap();
|
|
assert!(signature(&program, "read").effects.configuration);
|
|
assert!(signature(&program, "helper").pure);
|
|
assert_eq!(signature(&program, "read").failures.len(), 2);
|
|
for body in [
|
|
"setting.offset = value\nanswer = value",
|
|
"setting = value\nanswer = value",
|
|
"answer = setting.missing",
|
|
"answer = setting.offset()",
|
|
"answer = setting.token",
|
|
] {
|
|
let bad = format!(
|
|
"setting {{ offset: Int\ntoken: Secret }}\nport.read(value: Int) (answer: Int) {{ {body} }}"
|
|
);
|
|
assert!(
|
|
packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter", &bad)
|
|
])
|
|
.is_err(),
|
|
"{body}"
|
|
);
|
|
}
|
|
let empty =
|
|
"setting {}\nport.read(value: Int) (answer: Int) { setting = value\nanswer = value }";
|
|
assert!(
|
|
packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter", empty)
|
|
])
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn settings_reject_wrong_roles_invalid_wire_fields_and_shadowing() {
|
|
for owner in [
|
|
"main",
|
|
"shop.account.app",
|
|
"shop.account.port",
|
|
"shop.account.domain",
|
|
] {
|
|
assert!(
|
|
packages(&[(owner, "setting { offset: Int }")]).is_err(),
|
|
"{owner}"
|
|
);
|
|
}
|
|
for setting in [
|
|
"setting { Ready }",
|
|
"setting { private offset: Int }",
|
|
"setting { offset: Int > 0 }",
|
|
"setting { file: dever.system.File }",
|
|
"setting { values: Map<Text, Secret> }",
|
|
"setting { offset: Int }\nsetting { token: Secret }",
|
|
"setting { missing: Unknown }",
|
|
] {
|
|
let adapter =
|
|
format!("{setting}\nport.read(value: Int) (answer: Int) {{ answer = value }}");
|
|
assert!(
|
|
packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter", &adapter)
|
|
])
|
|
.is_err(),
|
|
"{setting}"
|
|
);
|
|
}
|
|
for helper in [
|
|
"helper(setting: Int) () {}",
|
|
"helper() (setting: Int) { setting = 1 }",
|
|
] {
|
|
let adapter = format!(
|
|
"setting {{ offset: Int }}\nport.read(value: Int) (answer: Int) {{ answer = value }}\n{helper}"
|
|
);
|
|
assert!(
|
|
packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter", &adapter)
|
|
])
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unused_adapter_candidates_still_enforce_failures_and_complete_contracts() {
|
|
let valid = "port.read(value: Int) (answer: Int) { answer = value }";
|
|
let invalid = "type Private { error Failed }\nport.read(value: Int) (answer: Int) { fail(Private.Failed()) }";
|
|
assert_eq!(
|
|
packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter.local", valid),
|
|
("shop.account.adapter.remote", invalid)
|
|
])
|
|
.unwrap_err()
|
|
.code,
|
|
"C012"
|
|
);
|
|
let extra_port = format!("{PORT}\nwrite(value: Int) () fails app.Problem");
|
|
let complete = format!("{valid}\nport.write(value: Int) () {{}}");
|
|
assert!(
|
|
packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", &extra_port),
|
|
("shop.account.adapter.local", &complete),
|
|
("shop.account.adapter.remote", valid)
|
|
])
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn selection_keeps_all_candidate_effects_and_failure_identity() {
|
|
let quiet = "setting { offset: Int }\nport.read(value: Int) (answer: Int) { answer = value + setting.offset }";
|
|
let scheduled = "port.read(value: Int) (answer: Int) { answer = wait(run(helper(value))) }\nhelper(value: Int) (answer: Int) { answer = value }";
|
|
let program = packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter.local", quiet),
|
|
("shop.account.adapter.remote", scheduled),
|
|
])
|
|
.unwrap();
|
|
let effects = signature(&program, "read").effects;
|
|
assert!(effects.configuration && effects.suspend && effects.schedule);
|
|
let pure = APP.replace("(answer: Int) {", "(answer: Int) pure {");
|
|
assert_eq!(
|
|
packages(&[
|
|
("shop.account.app", &pure),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter.local", quiet),
|
|
("shop.account.adapter.remote", scheduled),
|
|
])
|
|
.unwrap_err()
|
|
.code,
|
|
"C013"
|
|
);
|
|
let error = "port.read(value: Int) (answer: Int) { fail(app.Problem.Missing(\"missing\")) }";
|
|
let program = packages(&[
|
|
("shop.account.app", APP),
|
|
("shop.account.port", PORT),
|
|
("shop.account.adapter.local", quiet),
|
|
("shop.account.adapter.remote", error),
|
|
])
|
|
.unwrap();
|
|
assert_eq!(signature(&program, "read").failures.len(), 2);
|
|
assert!(signature(&program, "read").effects.configuration);
|
|
assert!(!program.api_snapshot().contains("setting.offset"));
|
|
}
|