use std::error::Error;
use serde_json::{Value, json};
use super::Row;
use crate::serve::contracts::Caller;
use crate::serve::protocol::{Change, Mutation, Version};
use crate::serve::storage::author_write;
type TestResult = Result<(), Box<dyn Error>>;
const MOST: usize = 1_000 * 64;
const KIBIBYTE: usize = 1_024;
const fn version(sequence: u64) -> Version {
Version {
generation: 7,
sequence,
}
}
fn id(index: usize) -> String {
format!("record-{index:05}")
}
fn node(index: usize) -> Value {
json!({"collection": "charts", "document_id": id(index)})
}
fn declaration() -> Value {
json!({"kind": "dense", "source": "body", "model": {"name": "local-model", "revision": "v1",
"manifest_sha256": "a".repeat(64)}, "dimensions": 2, "distance": "cosine"})
}
fn long(text: &str) -> String {
text.repeat(512 / text.len())
}
fn cases() -> Vec<(&'static str, Value, Value)> {
let mut cases = batch_cases();
cases.extend(single_cases());
cases
}
fn batch_cases() -> Vec<(&'static str, Value, Value)> {
let ids = |count: usize| (0..count).map(id).collect::<Vec<_>>();
let documents = |count: usize| {
(0..count)
.map(|index| json!({"document_id": id(index), "document": {"n": index}}))
.collect::<Vec<_>>()
};
let edges = |count: usize| {
(0..count)
.map(|index| json!({"relation": "link", "from": node(index), "to": node(index + 1), "properties": {}}))
.collect::<Vec<_>>()
};
let vectors = |count: usize| {
(0..count)
.map(|index| json!({"collection": "charts", "document_id": id(index), "field": "embedding", "vector": [1.0]}))
.collect::<Vec<_>>()
};
vec![
(
"documents.delete_many",
json!({"collection": "charts", "document_ids": ids(1)}),
json!({"collection": "charts", "document_ids": ids(MOST)}),
),
(
"documents.put_many",
json!({"collection": "charts", "documents": documents(1)}),
json!({"collection": "charts", "documents": documents(MOST)}),
),
(
"edges.put_many",
json!({"edges": edges(1)}),
json!({"edges": edges(MOST)}),
),
(
"vectors.put_many",
json!({"vectors": vectors(1)}),
json!({"vectors": vectors(MOST)}),
),
]
}
fn single_cases() -> Vec<(&'static str, Value, Value)> {
let big = long("x");
vec![
(
"document.put",
json!({"collection": "c", "document_id": "a", "document": {}}),
json!({"collection": big, "document_id": big, "document": {"body": long("body")}}),
),
(
"document.delete",
json!({"collection": "c", "document_id": "a"}),
json!({"collection": big, "document_id": big}),
),
(
"document.append_prune",
json!({"collection": "c", "scope": ["a"], "suffix": "s", "document": {}, "keep": 1}),
json!({"collection": big, "scope": vec!["s".repeat(48); 8], "suffix": "x".repeat(48), "document": {}, "keep": 1}),
),
(
"collection.put",
json!({"collection": "c", "schema": null}),
json!({"collection": big, "schema": null}),
),
(
"edge.put",
json!({"relation": "r", "from": {"collection": "c", "document_id": "a"},
"to": {"collection": "c", "document_id": "b"}, "properties": {}}),
json!({"relation": big, "from": {"collection": big, "document_id": big},
"to": {"collection": big, "document_id": big}, "properties": {}}),
),
(
"edge.delete",
json!({"relation": "r", "from": {"collection": "c", "document_id": "a"},
"to": {"collection": "c", "document_id": "b"}}),
json!({"relation": big, "from": {"collection": big, "document_id": big},
"to": {"collection": big, "document_id": big}}),
),
(
"vector.put",
json!({"collection": "c", "document_id": "a", "field": "f", "vector": [1.0]}),
json!({"collection": big, "document_id": big, "field": big, "vector": [1.0]}),
),
(
"vector.delete",
json!({"collection": "c", "document_id": "a", "field": "f"}),
json!({"collection": big, "document_id": big, "field": big}),
),
(
"vector.declare",
json!({"collection": "c", "field": "f", "declaration": declaration()}),
json!({"collection": big, "field": big, "declaration": declaration()}),
),
(
"index.declare",
json!({"collection": "c", "field": "f"}),
json!({"collection": big, "field": big}),
),
(
"relation.put",
json!({"relation": "r", "from_collection": "c", "to_collection": "c",
"cardinality": "many_to_many"}),
json!({"relation": big, "from_collection": big, "to_collection": big,
"cardinality": "many_to_many"}),
),
(
"relation.delete",
json!({"relation": "r"}),
json!({"relation": big}),
),
(
"branch.fork",
json!({"name": "w", "from": version(1)}),
json!({"name": big, "from": version(1)}),
),
("branch.discard", json!({"name": "w"}), json!({"name": big})),
(
"branch.merge",
json!({"from": "w", "reviewed": version(1)}),
json!({"from": big, "reviewed": version(1)}),
),
]
}
const fn kind_of(change: &Change) -> &'static str {
match change {
Change::Collection(_) => "collection.put",
Change::Put(_) => "document.put",
Change::AppendPrune(_) => "document.append_prune",
Change::Delete(_) => "document.delete",
Change::DeleteMany(_) => "documents.delete_many",
Change::Fork(_) => "branch.fork",
Change::Discard(_) => "branch.discard",
Change::Merge(_) => "branch.merge",
Change::EdgePut(_) => "edge.put",
Change::EdgeDelete(_) => "edge.delete",
Change::IndexDeclare(_) => "index.declare",
Change::VectorDeclare(_) => "vector.declare",
Change::VectorPut(_) => "vector.put",
Change::VectorPutMany(_) => "vectors.put_many",
Change::VectorDelete(_) => "vector.delete",
Change::PutMany(_) => "documents.put_many",
Change::EdgePutMany(_) => "edges.put_many",
Change::RelationPut(_) => "relation.put",
Change::RelationDelete(_) => "relation.delete",
}
}
const KINDS: usize = 19;
fn size(kind: &str, value: &Value) -> Result<usize, String> {
let change: Change = serde_json::from_value(json!({"kind": kind, "value": value}))
.map_err(|error| format!("{kind}: the change does not parse: {error}"))?;
if kind_of(&change) != kind {
return Err(format!("{kind} parsed as {}", kind_of(&change)));
}
let caller = Caller::new(
"https://lys.example",
"agent-7",
Some("clinician".to_owned()),
)
.map_err(|failure| failure.message)?;
let request = Mutation {
request_id: "request-1".to_owned(),
branch: "main".to_owned(),
expected: version(1),
change,
};
author_write(&request, &caller, version(2))
.map(|(_, bytes)| bytes.len())
.map_err(|failure| format!("{kind}: {}: {}", failure.code, failure.message))
}
#[test]
fn no_author_row_grows_with_its_change() {
let mut grown = Vec::new();
let mut named = std::collections::BTreeSet::new();
for (kind, small, large) in cases() {
named.insert(kind);
match (size(kind, &small), size(kind, &large)) {
(Ok(small), Ok(large)) if small == large && large < KIBIBYTE => {}
(small, large) => grown.push(format!("{kind}: small {small:?}, large {large:?}")),
}
}
assert_eq!(named.len(), KINDS, "{named:?}");
assert!(
grown.is_empty(),
"author rows that grow with their change:\n{}",
grown.join("\n")
);
}
#[test]
fn an_author_row_holds_who_and_never_what() -> TestResult {
let change: Change = serde_json::from_value(json!({"kind": "documents.delete_many",
"value": {"collection": "charts", "document_ids": ["secret-id-1", "secret-id-2"]}}))?;
let request = Mutation {
request_id: "delete-1".to_owned(),
branch: "main".to_owned(),
expected: version(1),
change,
};
let (key, bytes) = author_write(&request, &Caller::local("tom")?, version(2))?;
let stored: Value = serde_json::from_slice(&bytes)?;
let mut members: Vec<_> = stored
.as_object()
.ok_or("not an object")?
.keys()
.cloned()
.collect();
members.sort();
assert_eq!(
members,
["caller", "format", "origin", "request_id", "version"]
);
assert!(!stored.to_string().contains("secret-id"), "{stored}");
let decoded = Row::decode(&key, &bytes)?;
assert_eq!(
decoded.author(),
json!({"request_id": "delete-1", "caller": {"issuer": "local", "subject": "tom"}})
);
Ok(())
}