use super::{Directory, fixture}; use dever::{ compiler::{build, project::Project}, syntax::source::Source, }; #[test] fn native_calendar_and_clocks_keep_values_and_error_identity() { let directory = Directory::new(); let project = Project::load(&fixture().with_file_name("calendar.dever")).unwrap(); let program = project.check().unwrap(); for entry in ["verify", "clocks", "invalid"] { let binary = build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap(); let result = std::process::Command::new(binary).output().unwrap(); if entry == "invalid" { assert!(!result.status.success()); let error = String::from_utf8(result.stderr).unwrap(); assert!( error.contains("DateResult.Failed") && error.contains("lib/dever/time/calendar.dever:"), "{error}" ); } else { assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!(result.stdout, b"true\n"); } } } #[test] fn temporal_command_fields_round_trip_and_reject_invalid_wire_values() { let directory = Directory::new(); let mut project = Project::load(&fixture().with_file_name("application")).unwrap(); let source = project .sources .iter_mut() .find(|source| source.path.ends_with("greeting/app.dever")) .unwrap(); let start = source.text.find("type Profile").unwrap(); let end = source.text.find("type Problem").unwrap(); let fields = [ ("instant", "DateTime"), ("date", "Date"), ("time", "Time"), ("elapsed", "Duration"), ("optional", "DateTime?"), ]; let mut record = "type Profile {\n".to_owned(); for (name, kind) in fields { record.push_str(&format!(" {name}: {kind}\n")); } record.push_str("}\n\n"); let mut text = format!("{}{}{}", &source.text[..start], record, &source.text[end..]); let start = text.find("double(").unwrap(); let end = text.find("echo(").unwrap(); let replacement = r#"double(value: Duration) (answer: DateTime) { answer = domain.double(value) } "#; text.replace_range(start..end, replacement); *source = Source::new(source.id, source.path.clone(), text); let source = project .sources .iter_mut() .find(|source| source.path.ends_with("greeting/domain.dever")) .unwrap(); let start = source.text.find("double(").unwrap(); let end = source.text.find("positive(").unwrap(); let replacement = replacement.replace( "answer = domain.double(value)", "answer = dever.time.add(dever.time.parseDatetime(\"1970-01-01T00:00:00Z\"), value)", ); let mut text = source.text.clone(); text.replace_range(start..end, &replacement); *source = Source::new(source.id, source.path.clone(), text); let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "main", &directory.0.join("commands"), ) .unwrap(); let input = r#"{"value":{"instant":"2024-02-29T12:34:56.12+08:00","date":"2024-02-29","time":"12:34:56.000","elapsed":-3}}"#; let result = std::process::Command::new(&binary) .args(["demo.greeting.echo", input]) .output() .unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!( String::from_utf8(result.stdout).unwrap(), "{\"code\":0,\"message\":\"ok\",\"data\":{\"instant\":\"2024-02-29T04:34:56.120Z\",\"date\":\"2024-02-29\",\"time\":\"12:34:56\",\"elapsed\":-3,\"optional\":null}}\n" ); for invalid in [ input.replace("12:34:56.000", "12:34:56.1"), input.replace("2024-02-29\",\"time", "2023-02-29\",\"time"), input.replace("\"elapsed\":-3", "\"elapsed\":-3.0"), input.replace(".12+08:00", ".1234+08:00"), ] { let result = std::process::Command::new(&binary) .args(["demo.greeting.echo", &invalid]) .output() .unwrap(); assert!(!result.status.success(), "{invalid}"); assert!(result.stdout.is_empty()); assert!(String::from_utf8_lossy(&result.stderr).contains("invalid command input")); } let result = std::process::Command::new(&binary) .args(["demo.greeting.double", r#"{"value":9223372036854775807}"#]) .output() .unwrap(); assert!(!result.status.success()); assert!(result.stdout.is_empty()); assert!(String::from_utf8_lossy(&result.stderr).contains("four-digit UTC calendar range")); }