macro_rules! safe_writes_tests {
($db:ty, $value:ty) => {
use vantage_dataset::contract::{self, Fixture, OpFixture};
use vantage_dataset::prelude::*;
type Item = Table<$db, EmptyEntity>;
fn rec(pairs: &[(&str, $value)]) -> vantage_types::Record<$value> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect()
}
async fn fixture(test: &str) -> (Item, Item) {
let all = item(test).await;
all.insert_value(
"in1",
&rec(&[("name", "a".into()), ("parent", "p1".into())]),
)
.await
.unwrap();
all.insert_value(
"out1",
&rec(&[("name", "b".into()), ("parent", "p2".into())]),
)
.await
.unwrap();
let parent = all["parent"].clone();
let set = all.clone().with_condition(parent.eq("p1"));
(all, set)
}
confined_check!(delete_is_confined, check_delete, $value);
confined_check!(insert_is_confined, check_insert, $value);
confined_check!(patch_is_confined, check_patch, $value);
confined_check!(replace_is_confined, check_replace, $value);
confined_check!(delete_all_is_confined, check_delete_all, $value);
#[tokio::test]
async fn where_eq_fills_on_insert() {
let (all, set) = fixture("where_eq_fills_on_insert").await;
set.insert_value("n9", &rec(&[("name", "z".into())]))
.await
.unwrap();
assert_eq!(
all.get_value("n9").await.unwrap().unwrap()["parent"],
<$value>::from("p1".to_string())
);
}
#[tokio::test]
async fn operator_condition_keeps_rows_in_set() {
let all = item("operator_condition_keeps_rows_in_set").await;
all.insert_value(
"cheap",
&rec(&[("name", "c".into()), ("price", 5i64.into())]),
)
.await
.unwrap();
all.insert_value(
"dear",
&rec(&[("name", "d".into()), ("price", 20i64.into())]),
)
.await
.unwrap();
let price = all["price"].clone();
let set = all.clone().with_condition(price.gt(10i64));
contract::check_operator_condition(&OpFixture {
all: &all,
set: &set,
id: |s| s.to_string(),
text: |s| <$value>::from(s.to_string()),
int: |n| <$value>::from(n),
})
.await;
}
#[tokio::test]
async fn operator_condition_on_absent_column_is_a_conflict() {
let all = item("operator_condition_on_absent_column_is_a_conflict").await;
let price = all["price"].clone();
let set = all.clone().with_condition(price.gt(10i64));
let err = set
.insert_value("nop", &rec(&[("name", "n".into())]))
.await
.expect_err("a row without a price is outside `price > 10`");
assert!(err.is_conflict(), "expected a conflict, got: {err:?}");
assert!(all.get_value("nop").await.unwrap().is_none());
}
type Seen = std::sync::Arc<std::sync::Mutex<Vec<(Option<$value>, Option<$value>)>>>;
fn name_before_and_after(seen: Seen) -> vantage_table::prelude::Hook<$db> {
vantage_table::prelude::Hook::AfterUpdate(vantage_table::table::after_hook(
move |cx: vantage_table::table::AfterCx<'_, $db>| {
let pair = (
cx.before.and_then(|b| b.get("name").cloned()),
cx.record.get("name").cloned(),
);
let seen = seen.clone();
Box::pin(async move {
seen.lock().unwrap().push(pair);
Ok(())
})
},
))
}
async fn assert_after_update_sees_the_former_row(test: &str, patch: bool) {
let seen = Seen::default();
let (all, _) = fixture(test).await;
let hooked = all.with_hook(name_before_and_after(seen.clone()));
if patch {
hooked
.patch_value("in1", &rec(&[("name", "z".into())]))
.await
.unwrap();
} else {
hooked
.replace_value(
"in1",
&rec(&[("name", "z".into()), ("parent", "p1".into())]),
)
.await
.unwrap();
}
let text = |s: &str| Some(<$value>::from(s.to_string()));
assert_eq!(*seen.lock().unwrap(), vec![(text("a"), text("z"))]);
}
#[tokio::test]
async fn after_update_sees_the_former_row_on_replace() {
assert_after_update_sees_the_former_row("after_update_former_replace", false).await;
}
#[tokio::test]
async fn after_update_sees_the_former_row_on_patch() {
assert_after_update_sees_the_former_row("after_update_former_patch", true).await;
}
};
}
macro_rules! confined_check {
($name:ident, $check:ident, $value:ty) => {
#[tokio::test]
async fn $name() {
let (all, set) = fixture(stringify!($name)).await;
contract::$check(&Fixture {
all: &all,
set: &set,
id: |s| s.to_string(),
text: |s| <$value>::from(s.to_string()),
detects_outside: true,
})
.await;
}
};
}