use super::package::packages; const STORAGE: &str = "public type PutResult { Stored(key: Uuid)\nerror Failed(message: Text) }\npublic put(upload: Upload) (key: Uuid) { key = stored(dever.system.uploadPut(upload)) }\nstored(result: PutResult.Stored(value)) (key: Uuid) { key = value }\nstored(result: PutResult.Failed(message)) (key: Uuid) { fail(PutResult.Failed(message)) }"; const APP: &str = "accept(file: Upload, caption: Text) (size: Int, name: Text) { size = dever.api.uploadSize(file)\nname = caption + dever.api.uploadFilename(file)\ndever.api.closeUpload(file) }"; #[test] fn upload_metadata_then_close_preserves_a_single_owned_post_input() { let program = packages(&[ ("demo.media.app", APP), ("demo.media.api", "public post accept = app.accept"), ]) .unwrap(); assert!( dever::compiler::build::runtime_features(&program, &program.entries().collect()) .contains(&"upload") ); for api in ["public get accept = app.accept", "cmd accept = app.accept"] { assert!( packages(&[("demo.media.app", APP), ("demo.media.api", api)]).is_err(), "{api}" ); } } #[test] fn upload_rejects_duplication_nesting_output_and_helpers() { for app in [ "accept(file: Upload) () {}", "accept(file: Upload) () { dever.api.closeUpload(file)\ndever.api.closeUpload(file) }", "accept(file: Upload) (size: Int) { dever.api.closeUpload(file)\nsize = dever.api.uploadSize(file) }", "accept(file: Upload) () { copy = file\ndever.api.closeUpload(copy) }", "accept(file: Upload) (out: Upload) { out = file }", "type Container { file: Upload }", "type Container { Wrapped(file: Upload) }", "accept(files: List) () {}", "accept(file: Upload?) () {}", "accept(file: Upload, other: Upload) () { dever.api.closeUpload(file)\ndever.api.closeUpload(other) }", "accept(file: Upload) () { helper(file) }\nhelper(file: Upload) () { dever.api.closeUpload(file) }", ] { let error = packages(&[("demo.media.app", app)]).unwrap_err(); assert!( !error.code.starts_with('P'), "syntax did not reach ownership check: {error:?}" ); } assert!(packages(&[("demo.media.domain", "ignore(file: Upload) () {}")]).is_err()); } #[test] fn upload_storage_requires_a_same_domain_port_and_never_escapes_to_tasks() { let app = "accept(file: Upload) (key: Uuid) { key = port.store(file) }"; let port = "store(file: Upload) (key: Uuid) fails dever.storage.PutResult"; let adapter = "port.store(file: Upload) (key: Uuid) { key = dever.storage.put(file) }"; let checked = |app: &str| { packages(&[ ("dever.storage", STORAGE), ("demo.media.app", app), ("demo.media.port", port), ("demo.media.adapter.disk", adapter), ("demo.media.api", "public post accept = app.accept"), ]) }; checked(app).unwrap(); for app in [ "accept(file: Upload) (key: Uuid) { key = dever.storage.put(file) }", "accept(file: Upload) (key: Uuid) { task = run(port.store(file))\nkey = wait(task) }", "accept(file: Upload) (key: Uuid) { key = blocking(port.store(file)) }", "accept(file: Upload) (key: Uuid) { key = parallel(port.store(file)) }", ] { assert!(checked(app).is_err(), "{app}"); } } #[test] fn upload_storage_remains_an_external_effect_for_database_transactions() { let check = |prefix: &str, api: &str| { packages(&[ ("dever.storage", STORAGE), ("dever.database", super::database::ERROR), ("demo.media.model", "type Entry { name: Text }"), ( "demo.media.app", &format!( "{prefix}accept(file: Upload) (key: Uuid) {{ row = model.create({{ name = \"upload\" }})\nkey = port.store(file) }}" ), ), ( "demo.media.port", "store(file: Upload) (key: Uuid) fails dever.storage.PutResult", ), ( "demo.media.adapter", "port.store(file: Upload) (key: Uuid) { key = dever.storage.put(file) }", ), ("demo.media.api", api), ]) }; assert_eq!( check("", "public post accept = app.accept") .unwrap_err() .code, "C014" ); assert_eq!( check("transaction ", "public post accept = app.accept") .unwrap_err() .code, "C014" ); assert_eq!( check("", "public long post accept = app.accept") .unwrap_err() .code, "C014" ); }