use super::package::packages; use dever::{compiler::model::Program, syntax::diagnostic::Diagnostic}; mod defaults; mod native; mod privacy; mod query; mod relation; mod scalar; mod schema; pub(super) const ERROR: &str = "public type Error { error Pool(message: Text)\nerror PoolExhausted(message: Text)\nerror Connection(message: Text)\nerror Timeout(message: Text)\nerror Cancelled(message: Text)\nerror Database(message: Text)\nerror Constraint(message: Text)\nerror NotFound(message: Text)\nerror InvalidData(message: Text)\nerror Migration(message: Text) }"; fn checked(app: &str, api: &str) -> Result { packages(&[ ("dever.database", ERROR), ( "demo.account.model", "type Account { name: Text(1, 64) unique\nnote: Text? }", ), ("demo.account.app", app), ("demo.account.api", api), ]) } #[test] fn model_ids_queries_and_failures_keep_static_identity() { let program = checked( "get(id: model.id) (account: model.Account) { account = model.get(id) }", "cmd get = app.get", ) .unwrap(); let function = &program.functions[0].signature; assert!(function.effects.database); assert!(!function.effects.database_write); assert_eq!(function.failures.len(), 10); assert_eq!(program.models.len(), 1); assert!(program.models[0].columns[2].unique); assert_eq!(program.models[0].columns[2].limits, Some((1, 64))); assert_eq!( checked( "get(id: Int) (account: model.Account) { account = model.get(id) }", "cmd get = app.get" ) .unwrap_err() .code, "C005" ); let app = "read(value: model.Account) (id: model.id) { value.id = value.id\nid = value.id }"; assert_eq!( checked(app, "cmd read = app.read").unwrap_err().code, "C014" ); assert_eq!(checked("create(name: Text) (account: model.Account) { account = model.create({ name = name }) }", "public get create = app.create").unwrap_err().code, "C014"); } #[test] fn transactions_reject_unrelated_work_and_role_bypasses() { let crypto = "public type HashResult { Ready(value: Text)\nerror Failed(message: Text) }\npublic passwordHash(secret: Secret) (hash: Text) { hash = hashValue(dever.system.cryptoPasswordHash(secret)) }\nhashValue(result: HashResult.Ready(value)) (hash: Text) { hash = value }\nhashValue(result: HashResult.Failed(message)) (hash: Text) { fail(result) }"; let blocked = packages(&[ ("dever.database", ERROR), ("dever.crypto", crypto), ("demo.account.model", "type Account { name: Text }"), ( "demo.account.app", "transaction create(password: Secret) (account: model.Account) { hash = blocking(dever.crypto.passwordHash(password))\naccount = model.create({ name = hash }) }", ), ("demo.account.api", "cmd create = app.create"), ]); assert_eq!(blocked.unwrap_err().code, "C014"); let staged = packages(&[ ("dever.database", ERROR), ("dever.crypto", crypto), ("demo.account.model", "type Account { name: Text }"), ("demo.account.app", "transaction persist(name: Text) (account: model.Account) { account = model.create({ name = name }) }\ncreate(password: Secret) (account: model.Account) { hash = blocking(dever.crypto.passwordHash(password))\naccount = persist(hash) }"), ]).unwrap(); let staged = &staged .functions .iter() .find(|function| function.signature.name == "create") .unwrap() .signature .effects; assert!(staged.password); assert!(!staged.unscoped_write); let app = "transaction create(name: Text) (account: model.Account) { account = model.create({ name = name }) }"; assert!(checked(app, "long cmd create = app.create").is_ok()); assert!(checked(app, "public long post create = app.create").is_ok()); assert_eq!(checked("create(name: Text) (account: model.Account) { account = model.create({ name = name }) }", "long cmd create = app.create").unwrap_err().code, "C014"); assert_eq!(checked("create(name: Text) (account: model.Account) { account = model.create({ name = name }) }", "public long post create = app.create").unwrap_err().code, "C014"); assert!( checked( "read(value: model.Page) (page: model.Page) { value.page = 2\npage = value }", "cmd read = app.read" ) .is_ok() ); assert_eq!( checked( "transaction empty(value: Int) (answer: Int) { answer = value }", "cmd empty = app.empty" ) .unwrap_err() .code, "C014" ); assert_eq!(checked("read() (total: Int) { total = model.count() }\ntransaction spawn() (total: Int) { work = run(read())\ntotal = wait(work) }", "cmd spawn = app.spawn").unwrap_err().code, "C014"); assert_eq!( packages(&[ ("dever.database", ERROR), ("demo.account.model", "type Account { name: Text }"), ( "demo.account.domain", "read() (total: Int) { total = model.count() }" ) ]) .unwrap_err() .code, "C006" ); assert_eq!( packages(&[("main", "type Value { name: Text(64) }")]) .unwrap_err() .code, "C014" ); assert_eq!( packages(&[("main", "type Value { name: Text unique }")]) .unwrap_err() .code, "C014" ); } #[test] fn model_query_blocks_reject_dynamic_fields_and_invalid_options() { for query in [ "{ where = missing == name }", "{ where = name }", "{ order = [missing.asc] }", "{ size = 1, size = 2 }", "{ size = name }", ] { let app = format!("search(name: Text) (page: model.Page) {{ page = model.list({query}) }}"); assert!(checked(&app, "cmd search = app.search").is_err(), "{query}"); } assert_eq!( packages(&[( "demo.account.model", "database one\ndatabase two\ntype Account { name: Text }" )]) .unwrap_err() .code, "C002" ); assert_eq!( packages(&[("demo.account.model", "type Account { name: Text(65, 64) }")]) .unwrap_err() .code, "C014" ); }