Files
core/test/rust/compiler/database/privacy.rs
T
2026-11-03 03:32:07 +00:00

114 lines
4.0 KiB
Rust

use super::{ERROR, packages};
fn check(app: &str) -> Result<super::Program, super::Diagnostic> {
packages(&[
("dever.database", ERROR),
(
"demo.account.model",
"type Account { name: Text\nprivate token: Text }",
),
("demo.account.app", app),
])
}
#[test]
fn private_model_snapshots_cannot_cross_app_signatures() {
for kind in [
"model.Account",
"model.Account?",
"List<model.Account>",
"model.Page",
"Map<Text, model.Account>",
"MapEntry<Text, model.Account>",
"AsyncStream<model.Account>",
"Related<model.Account>",
] {
let body = format!("read(value: {kind}) (answer: {kind}) {{ answer = value }}");
let error = check(&body).unwrap_err();
assert_eq!(error.code, "C006", "{kind}: {error:?}");
assert!(error.message.contains("private Model"), "{error:?}");
}
let error = check("read(id: model.id) (account: model.Account) { account = model.get(id) }")
.unwrap_err();
assert_eq!(error.code, "C006");
}
#[test]
fn private_models_cannot_hide_in_app_records_or_errors() {
for body in [
"type Wrapped { private value: model.Account }\nread(value: Wrapped) (answer: Wrapped) { answer = value }",
"type Problem { error Denied(account: model.Account) }\nread(id: model.id) () { account = model.get(id)\nfail(Problem.Denied(account)) }",
"read(action: handler(value: model.Account) ()) () {}",
] {
let error = check(body).unwrap_err();
assert_eq!(error.code, "C006", "{error:?}");
assert!(error.message.contains("private Model"), "{error:?}");
}
}
#[test]
fn opaque_public_records_remain_legal_but_private_fields_stay_off_wire() {
let opaque = "type Session { private token: Text\nlabel: Text }\nread(value: Session) (answer: Session) { answer = value }";
assert!(check(opaque).is_ok());
let error = packages(&[
("demo.account.app", opaque),
("demo.account.api", "cmd read = app.read"),
])
.unwrap_err();
assert_eq!(error.code, "C006");
assert!(error.message.contains("private fields"), "{error:?}");
assert!(check("type View { name: Text }\nread(id: model.id) (view: View) { account = model.get(id)\nview = View { name = account.name } }").is_ok());
let hidden = [
("demo.account.domain", "type State { value: Text }"),
(
"demo.account.app",
"type Session { private state: domain.State\nlabel: Text }",
),
];
let mut files = hidden.to_vec();
files.push((
"demo.other.app",
"pass(value: demo.account.Session) (answer: demo.account.Session) { answer = value }",
));
assert!(packages(&files).is_ok());
files.pop();
files.push((
"demo.other.app",
"read(value: demo.account.Session) (answer: Text) { answer = value.state.value }",
));
assert_eq!(packages(&files).unwrap_err().code, "C006");
}
#[test]
fn private_model_data_propagates_through_related_cycles_without_blocking_ids() {
let models = [
("dever.database", ERROR),
(
"demo.people.model.team",
"type Team { name: Text }\nrelation users = demo.people.model.user.team_id",
),
(
"demo.people.model.user",
"type User { private token: Text\nteam_id: model.team.id }",
),
];
for kind in [
"model.team.Team",
"Related<model.team.Team>",
"List<model.team.Team?>",
] {
let app = format!("pass(value: {kind}) (answer: {kind}) {{ answer = value }}");
let mut files = models.to_vec();
files.push(("demo.people.app", &app));
let error = packages(&files).unwrap_err();
assert_eq!(error.code, "C006", "{error:?}");
assert!(error.message.contains("private Model"), "{error:?}");
}
let mut files = models.to_vec();
files.push((
"demo.people.app",
"pass(value: model.user.id) (answer: model.user.id) { answer = value }",
));
assert!(packages(&files).is_ok());
}