104 lines
4.3 KiB
Rust
104 lines
4.3 KiB
Rust
use super::{APP, ERROR, packages};
|
|
|
|
const CLOCK: &str = "public type ClockResult { Read(value: Int)\nerror Failed(message: Text) }\npublic unixMillis() (value: Int) { value = read(dever.system.timeUnixMillis()) }\nread(result: ClockResult.Read(number)) (value: Int) { value = number }\nread(result: ClockResult.Failed(message)) (value: Int) { fail(ClockResult.Failed(message)) }\npublic type SleepResult { Done\nerror Failed(message: Text) }\npublic sleep(millis: Int) () { slept(dever.system.timeSleep(millis)) }\nslept(result: SleepResult.Done) () {}\nslept(result: SleepResult.Failed(message)) () { fail(SleepResult.Failed(message)) }";
|
|
const TASK: &str = "public sleep(millis: Int) () { slept(dever.system.taskSleep(millis)) }\nslept(result: dever.time.SleepResult.Done) () {}\nslept(result: dever.time.SleepResult.Failed(message)) () { fail(dever.time.SleepResult.Failed(message)) }";
|
|
|
|
#[test]
|
|
fn provider_expiration_reads_clock_but_cannot_wait() {
|
|
for (operation, error) in [
|
|
("current = dever.time.unixMillis()", None),
|
|
("dever.time.sleep(1)\ncurrent = 0", Some("C013")),
|
|
("dever.task.sleep(1)\ncurrent = 0", Some("C014")),
|
|
] {
|
|
let app = APP
|
|
.replace(
|
|
"account = model.first",
|
|
&format!("{operation}\naccount = model.first"),
|
|
)
|
|
.replace(
|
|
"session == claims.session",
|
|
"session == claims.session and expires_at > current",
|
|
);
|
|
let result = packages(&[
|
|
("dever.database", ERROR),
|
|
("dever.api", "public type Error { error Unauthorized }"),
|
|
(
|
|
"dever.auth",
|
|
"public type Claims { subject: Text\nsession: Text\ntenant: Text?\nsite: Text }",
|
|
),
|
|
("dever.time", CLOCK),
|
|
("dever.task", TASK),
|
|
(
|
|
"demo.account.model",
|
|
"global type Account { subject: Text\nsession: Text\nexpires_at: Int }",
|
|
),
|
|
("demo.account.app", &app),
|
|
("demo.account.api", "public get health = app.health"),
|
|
]);
|
|
match error {
|
|
Some(code) => assert_eq!(result.unwrap_err().code, code, "{operation}"),
|
|
None => {
|
|
let program = result.unwrap();
|
|
let effects = program.functions[program.verifiers[0].function]
|
|
.signature
|
|
.effects;
|
|
assert!(effects.clock);
|
|
assert!(!effects.timer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn shipped_session_expiration_contract_checks_without_dropping_the_deadline() {
|
|
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../skills/assets/examples/http-rest");
|
|
let project = dever::compiler::project::Project::load(&root).unwrap();
|
|
let program = project.check().unwrap();
|
|
assert!(
|
|
program
|
|
.verifiers
|
|
.iter()
|
|
.any(|verifier| { program.functions[verifier.function].signature.effects.clock })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn provider_checks_every_model_loaded_by_a_relation() {
|
|
let model =
|
|
"global type Account { subject: Text\nsession: Text\nprofile_id: demo.profile.model.id }";
|
|
let app = APP.replace(
|
|
"session == claims.session })",
|
|
"session == claims.session, with = [profile] })",
|
|
);
|
|
for global in [true, false] {
|
|
let profile = format!(
|
|
"{}type Profile {{ name: Text }}",
|
|
if global { "global " } else { "" }
|
|
);
|
|
let result = packages(&[
|
|
("dever.database", ERROR),
|
|
("dever.api", "public type Error { error Unauthorized }"),
|
|
(
|
|
"dever.auth",
|
|
"public type Claims { subject: Text\nsession: Text\ntenant: Text?\nsite: Text }",
|
|
),
|
|
("demo.account.model", model),
|
|
("demo.profile.model", &profile),
|
|
("demo.account.app", &app),
|
|
("demo.account.api", "public get health = app.health"),
|
|
]);
|
|
if global {
|
|
result.unwrap();
|
|
} else {
|
|
let error = result.unwrap_err();
|
|
assert_eq!(error.code, "C014");
|
|
assert!(
|
|
error
|
|
.message
|
|
.contains("Provider verify cannot read a tenant Model")
|
|
);
|
|
}
|
|
}
|
|
}
|