use anyhow::Result;
use surrealdb_types::Value;
use test_log::test;
use crate::catalog::providers::CatalogProvider;
use crate::dbs::{NewPlannerStrategy, Session};
use crate::iam::{Level, Role};
use crate::kvs::{Datastore, TransactionType};
async fn read_root_only_computed_field(
strategy: NewPlannerStrategy,
) -> Result<Result<Value, surrealdb_types::Error>> {
let ds = Datastore::new("memory").await?;
{
let tx = ds.transaction(TransactionType::Write).await?;
tx.ensure_ns_db(None, "test", "test").await?;
tx.commit().await?;
}
let root = Session::owner().with_ns("test").with_db("test");
for response in
ds.execute("DEFINE TABLE thing SCHEMALESS; CREATE thing:1;", &root, None).await?
{
response.result?;
}
let definer =
Session::for_level(Level::Database("test".to_owned(), "test".to_owned()), Role::Editor);
for response in
ds.execute("DEFINE FIELD leak ON thing COMPUTED (INFO FOR ROOT);", &definer, None).await?
{
response.result?;
}
let reader = Session::owner().with_ns("test").with_db("test").new_planner_strategy(strategy);
let mut responses = ds.execute("SELECT leak FROM thing:1;", &reader, None).await?;
Ok(responses.remove(0).result)
}
fn outcome(result: Result<Value, surrealdb_types::Error>) -> Result<Vec<String>, String> {
let value = match result {
Ok(value) => value,
Err(err) => return Err(err.to_string()),
};
let Value::Array(rows) = value else {
return Err(format!("expected an array of rows, got {value:?}"));
};
let Some(Value::Object(row)) = rows.first().cloned() else {
return Err(format!("expected one object row, got {rows:?}"));
};
let Some(Value::Object(info)) = row.get("leak").cloned() else {
return Err(format!("expected an object in `leak`, got {row:?}"));
};
let Some(Value::Object(namespaces)) = info.get("namespaces").cloned() else {
return Err(format!("expected `namespaces` in the root info, got {info:?}"));
};
let mut names: Vec<String> = namespaces.keys().cloned().collect();
names.sort();
Ok(names)
}
#[test(tokio::test(flavor = "multi_thread"))]
async fn both_engines_narrow_a_read_time_computed_body_to_the_definer() -> Result<()> {
let legacy = outcome(read_root_only_computed_field(NewPlannerStrategy::ComputeOnly).await?);
let streaming =
outcome(read_root_only_computed_field(NewPlannerStrategy::AllReadOnlyStatements).await?);
assert_eq!(
legacy, streaming,
"the two engines disagree on the auth used for a read-time COMPUTED body"
);
let err = legacy.expect_err("a (Database, Editor) definer must not reach INFO FOR ROOT");
assert!(err.contains("Not enough permissions"), "expected a permission refusal, got: {err}");
Ok(())
}