162 lines
6.2 KiB
Rust
162 lines
6.2 KiB
Rust
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<Program, Diagnostic> {
|
|
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"
|
|
);
|
|
}
|