use dever::{ compiler::{check, model::Program}, syntax::{diagnostic::Diagnostic, source::Source}, }; fn linked(text: &str) -> Result { let inputs = [ ("main", text), ( "dever.crypto", include_str!("../../../lib/dever/crypto.dever"), ), ("dever.time", include_str!("../../../lib/dever/time.dever")), ("dever.task", include_str!("../../../lib/dever/task.dever")), ]; let modules = inputs .iter() .enumerate() .map(|(index, (_, text))| { dever::syntax::parse(&Source::new( index, if index == 0 { "main.dever".into() } else { format!("library{index}.dever") }, (*text).into(), )) }) .collect::, _>>()?; check::check( modules, &inputs .iter() .map(|(package, _)| (*package).into()) .collect::>(), ) } #[test] fn secrets_cannot_be_compared_converted_or_hidden_in_error_payloads() { for source in [ "main(value: Secret) (answer: Bool) { answer = value == value }", "main(values: List) (answer: Bool) { answer = values == values }", "main(value: Secret) (answer: Int) { answer = length(value) }", "main(value: Secret) (answer: Text) { answer = value }", "type Invalid { error Failed(value: Secret) }\nmain() () {}", "type Inner { key: Secret }\ntype Invalid { error Failed(values: List) }\nmain() () {}", "main(values: Map) () {}", ] { assert_eq!(linked(source).unwrap_err().code, "C005", "{source}"); } let error = linked("main() () { value = secret(\"password\") }").unwrap_err(); assert_eq!(error.code, "C006"); assert!(error.message.contains("compiler-owned Test source")); assert!(linked("main(value: Secret) (answer: Secret?) { answer = value }").is_ok()); } #[test] fn crypto_randomness_and_password_work_keep_effect_boundaries() { for source in [ "main() () pure { value = dever.crypto.token(16) }", "main(password: Secret) (hash: Text) { dever.task.sleep(0)\n hash = dever.crypto.passwordHash(password) }", "main(password: Secret, hash: Text) (valid: Bool) { dever.task.sleep(0)\n valid = dever.crypto.passwordVerify(password, hash) }", ] { assert_eq!(linked(source).unwrap_err().code, "C013", "{source}"); } assert!(linked("main(password: Secret) (hash: Text) { hash = blocking(dever.crypto.passwordHash(password)) }").is_ok()); assert!(linked("main(key: Secret, bytes: Bytes) (hash: Bytes) pure { hash = dever.crypto.hmacSha256(key, bytes) }").is_ok()); assert_eq!( linked("main() () { value = dever.system.cryptoToken(16) }") .unwrap_err() .code, "C006" ); assert_eq!( linked("main() () { lost = result(dever.crypto.token(0)) }") .unwrap_err() .code, "C012" ); } #[test] fn app_outputs_reject_nested_secret_types() { for source in [ "read() (value: Secret?) { value = null }", "type Envelope { values: List }\nread() (value: Envelope) { value = Envelope { values = [] } }", ] { let module = dever::syntax::parse(&Source::new(0, "app.dever", source.into())).unwrap(); let error = check::check(vec![module], &["shop.account.app".into()]).unwrap_err(); assert_eq!(error.code, "C006"); assert!(error.message.contains("Secret")); } }