use std::collections::BTreeSet;
use std::path::PathBuf;
use serde_json::Value;
use super::{Class, ENVELOPES, ROUTES, render};
use crate::serve::protocol::{Change, Operation};
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn design_file(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../docs/design/access")
.join(name)
}
fn schema() -> Result<Value, Box<dyn std::error::Error>> {
Ok(serde_json::from_slice(&std::fs::read(design_file(
"schema.json",
))?)?)
}
#[test]
fn every_served_route_has_exactly_one_row() {
let served: BTreeSet<&str> = Operation::WIRE_NAMES
.iter()
.chain(Change::WIRE_NAMES)
.copied()
.filter(|wire| !ENVELOPES.contains(wire))
.collect();
let rows: Vec<&str> = ROUTES.iter().map(|route| route.wire).collect();
let unique: BTreeSet<&str> = rows.iter().copied().collect();
assert_eq!(rows.len(), unique.len(), "a route has two rows: {rows:?}");
let missing: Vec<&&str> = served.difference(&unique).collect();
let extra: Vec<&&str> = unique.difference(&served).collect();
assert!(
missing.is_empty(),
"served routes without a row: {missing:?}"
);
assert!(
extra.is_empty(),
"rows for routes nothing serves: {extra:?}"
);
for envelope in ENVELOPES {
assert!(
Operation::WIRE_NAMES.contains(envelope),
"the envelope {envelope} is not a served method"
);
}
}
#[test]
fn every_row_names_an_action_its_kind_declares() -> TestResult {
let schema = schema()?;
for route in ROUTES {
let kind = format!("haematite.{}", route.kind);
let actions = schema["kinds"][&kind]["actions"]
.as_array()
.ok_or_else(|| format!("{}: the schema has no kind {kind}", route.wire))?;
assert!(
actions.iter().any(|action| action == route.action),
"{}: {kind} does not declare {}",
route.wire,
route.action
);
}
Ok(())
}
#[test]
fn every_row_has_the_class_its_schema_registers() -> TestResult {
let schema = schema()?;
let kinds = schema["kinds"]
.as_object()
.ok_or("the schema has no kinds")?;
for (kind, body) in kinds {
let hot: BTreeSet<&str> = body["hot"]
.as_array()
.map(|listed| listed.iter().filter_map(Value::as_str).collect())
.unwrap_or_default();
for action in body["actions"].as_array().ok_or("a kind with no actions")? {
let action = action.as_str().ok_or("an action that is not a string")?;
let fixed = matches!(action, "read" | "query" | "write");
assert_eq!(
hot.contains(action),
fixed,
"{kind} registers {action} in the wrong class"
);
}
}
for route in ROUTES {
let kind = format!("haematite.{}", route.kind);
let registered = schema["kinds"][&kind]["hot"]
.as_array()
.is_some_and(|hot| hot.iter().any(|action| action == route.action));
let expected = if registered {
Class::Hot
} else {
Class::Deliberate
};
assert_eq!(route.class, expected, "{} is misclassified", route.wire);
}
Ok(())
}
#[test]
fn the_observer_never_writes_and_the_writers_only_deliberate_act_is_branch() -> TestResult {
let schema = schema()?;
let relations = &schema["kinds"]["haematite.database"]["relations"];
let carried = |role: &str| -> Result<BTreeSet<String>, String> {
relations[role]
.as_array()
.ok_or_else(|| format!("no role {role}"))?
.iter()
.map(|action| {
action
.as_str()
.map(str::to_owned)
.ok_or_else(|| format!("{role} carries a non-string action"))
})
.collect()
};
let words = |list: &[&str]| -> BTreeSet<String> {
list.iter().map(|word| (*word).to_owned()).collect()
};
assert_eq!(carried("observer")?, words(&["query", "read"]));
let writer = carried("writer")?;
assert_eq!(writer, words(&["branch", "query", "read", "write"]));
let writers_deliberate: BTreeSet<String> = ROUTES
.iter()
.filter(|route| route.class == Class::Deliberate && writer.contains(route.action))
.map(|route| route.action.to_owned())
.collect();
assert_eq!(writers_deliberate, words(&["branch"]));
assert!(!writer.contains("merge"), "a writer never merges");
assert!(
carried("administrator")?.contains("merge"),
"the administrator merges"
);
let declared: BTreeSet<String> = schema["kinds"]["haematite.database"]["actions"]
.as_array()
.ok_or("the database kind declares no actions")?
.iter()
.filter_map(|action| action.as_str().map(str::to_owned))
.collect();
assert_eq!(carried("administrator")?, declared);
Ok(())
}
#[test]
fn the_committed_matrix_is_the_rendered_rows() -> TestResult {
let committed = std::fs::read_to_string(design_file("MATRIX.md"))?;
assert_eq!(
committed,
render(),
"docs/design/access/MATRIX.md differs from the route rows; render it again"
);
Ok(())
}
#[test]
fn a_writer_reviews_an_import_branch_and_never_merges() -> TestResult {
let schema = schema()?;
let writer: BTreeSet<&str> = schema["kinds"]["haematite.database"]["relations"]["writer"]
.as_array()
.ok_or("no writer role")?
.iter()
.filter_map(Value::as_str)
.collect();
for method in [
"branch.review",
"branch.review_counts",
"branch.merge_prepare",
"branch.merge_dangling",
] {
let route = ROUTES
.iter()
.find(|route| route.wire == method)
.ok_or_else(|| format!("no row for {method}"))?;
assert_eq!(
(route.action, route.class),
("read", Class::Hot),
"{method}"
);
assert!(writer.contains(route.action), "a writer cannot {method}");
}
assert!(!writer.contains("merge"), "a writer merges");
assert!(
ROUTES
.iter()
.filter(|route| route.action == "merge")
.all(|route| !writer.contains(route.action)),
"a route a writer reaches carries merge"
);
Ok(())
}