use super::{checked, package::packages}; #[test] fn api_snapshot_uses_declared_contracts_not_private_bodies_or_inferred_constants() { let snapshot = |body: &str| { checked(&format!("public value(input: Int) (answer: Int) {{ answer = helper(input) }}\nhelper(input: Int) (answer: Int) {{ {body} }}")).unwrap().api_snapshot() }; let first = snapshot("answer = 1"); assert_eq!( first, "dever-api 5\nfunction main.value(Int) (answer: Int []) pure=false recover=None effects=[] errors=[] recoveries={}\n" ); assert_eq!(first, snapshot("answer = input + 2")); let constrained = checked("public value(input: Int) (answer: Int >= 0) { answer = 1 }").unwrap(); assert!( constrained .api_snapshot() .contains("answer: Int [0..9223372036854775807]") ); let difference = constrained.check_api(&first).unwrap_err(); assert!( difference.contains("- function main.value") && difference.contains("+ function main.value") ); } #[test] fn api_snapshot_tracks_public_shape_construction_failure_and_transitive_recovery() { let snapshot = |key: &str| { checked(&format!( "public type Session {{ private key: {key}\nlabel: Text }}" )) .unwrap() .api_snapshot() }; let private = snapshot("Int"); assert_eq!(private, snapshot("Text")); assert_eq!( private, "dever-api 5\ntype main.Session record construct=private {label: Text []}\n" ); let exposed = checked("public type Session { key: Int\nlabel: Text }").unwrap(); assert!( exposed .check_api(&private) .unwrap_err() .contains("construct=public") ); let failing = checked("public type Problem { error Bad(message: Text) }\npublic value() () { fail(Problem.Bad(\"bad\")) }").unwrap(); let baseline = failing.api_snapshot(); assert!(baseline.contains("errors=[main.Problem.Bad]")); assert!(baseline.contains("type main.Problem error Bad(message: Text [])")); let snapshot = |reason: &str| { checked(&format!( "public value() () {{ helper() }}\nhelper() () recover(\"{reason}\") {{}}" )) .unwrap() .api_snapshot() }; assert_ne!(snapshot("use zero"), snapshot("use one")); assert!(snapshot("use zero").contains("recoveries={\"use zero\"}")); } #[test] fn api_snapshot_is_stable_across_app_topics_and_binds_command_inputs() { let snapshot = |topic: &str, input: &str| { packages(&[ ( topic, &format!("value({input}: Int) (answer: Int) {{ answer = {input} }}"), ), ("shop.account.api", "cmd value = app.value"), ]) .unwrap() .api_snapshot() }; assert_eq!( snapshot("shop.account.app", "input"), snapshot("shop.account.app.read", "input") ); assert_ne!( snapshot("shop.account.app", "input"), snapshot("shop.account.app", "renamed") ); assert!( snapshot("shop.account.app", "input") .contains("cmd shop.account.value = shop.account.value(input: Int [])") ); let program = checked("public apply(action: handler(input: Int) (answer: Int), input: Int) (answer: Int) { answer = action(input) }").unwrap(); assert!( program .api_snapshot() .contains("function main.apply(handler(Int [])(answer: Int []), Int)") ); } #[test] fn public_handler_snapshot_keeps_constraints_and_ignores_specializations() { let source = "public apply(action: handler(input: Int >= 0) (answer: Int >= 0)) (answer: Int >= 0) { answer = action(1) }"; let original = checked(source).unwrap().api_snapshot(); assert!( original.contains( "handler(Int [0..9223372036854775807])(answer: Int [0..9223372036854775807])" ) ); let called = checked(&format!("{source}\nnonnegative(input: Int) (answer: Int >= 0) {{ answer = 0 }}\nmain() (answer: Int) {{ answer = apply(nonnegative) }}")).unwrap(); assert_eq!(original, called.api_snapshot()); }