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") ); } } }