use std::io;
use serde_json::{Value, json};
use super::{ServiceRefusal, dispatch, writes_tests::Fixture};
use crate::mcp::{rpc, types};
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn call(arguments: &Value) -> Result<rpc::ToolCall, serde_json::Error> {
Ok(rpc::ToolCall {
metadata: None,
name: "describe_store".to_owned(),
arguments: serde_json::value::to_raw_value(arguments)?,
})
}
#[test]
fn describe_store_projects_real_served_metadata_at_one_captured_version() -> TestResult {
let mut fixture = Fixture::start()?;
fixture.seed()?;
let expected = fixture.current()?;
let before = fixture.requests();
let input = call(
&json!({"selection":{"branch":"main","version":null},"page":{"rows":64,"bytes":65536},"after":{}}),
)?;
let result = dispatch(&mut fixture.sender, &input)?;
assert_eq!(fixture.requests(), before + 5);
assert!(!result.is_error, "{}", result.structured_content.get());
let output: types::DescribeStoreOutput = serde_json::from_str(result.structured_content.get())?;
assert_eq!(output.selection.branch, "main");
assert_eq!(output.selection.version.0, expected);
assert_eq!(
output
.collections
.entries
.first()
.map(|entry| entry.name.as_str()),
Some("records")
);
assert_eq!(
output
.relations
.entries
.first()
.map(|entry| entry.relation.as_str()),
Some("links")
);
assert!(output.vector_fields.entries.is_empty());
assert_eq!(output.counts.collections, 1);
assert_eq!(output.counts.documents, 2);
assert_eq!(output.counts.edges, 0);
assert_eq!(output.counts.vectors, 0);
let schema = crate::mcp::schema::tools()
.into_iter()
.find(|tool| tool.name == "describe_store")
.ok_or_else(|| io::Error::other("description schema missing"))?;
let validator = jsonschema::validator_for(schema.output_schema.as_value())?;
let body: Value = serde_json::from_str(result.structured_content.get())?;
validator
.validate(&body)
.map_err(|error| io::Error::other(error.to_string()))?;
fixture.finish()
}
#[test]
fn describe_store_refuses_unbound_cursor_and_exceeded_output_bytes() -> TestResult {
let mut fixture = Fixture::start()?;
fixture.seed()?;
let unbound = call(
&json!({"selection":{"branch":"main","version":null},"page":{"rows":64,"bytes":65536},"after":{"relations":"not-a-cursor"}}),
)?;
let result = dispatch(&mut fixture.sender, &unbound)?;
assert!(result.is_error);
let refusal: ServiceRefusal = serde_json::from_str(result.structured_content.get())?;
assert_eq!(refusal.code, "mcp_version_required");
let short = call(
&json!({"selection":{"branch":"main","version":null},"page":{"rows":64,"bytes":1},"after":{}}),
)?;
let result = dispatch(&mut fixture.sender, &short)?;
assert!(result.is_error);
let refusal: ServiceRefusal = serde_json::from_str(result.structured_content.get())?;
assert_eq!(refusal.code, "mcp_page_bytes_exceeded");
fixture.finish()
}
fn collection(fixture: &mut Fixture, name: &str) -> TestResult {
let input = rpc::ToolCall {
metadata: None,
name: "define_collection".to_owned(),
arguments: serde_json::value::to_raw_value(&json!({
"request_id":format!("collection-{name}"),
"branch":"main",
"expected":fixture.current()?,
"collection":name,
"schema":null
}))?,
};
let result = dispatch(&mut fixture.sender, &input)?;
assert!(!result.is_error, "{}", result.structured_content.get());
Ok(())
}
#[test]
fn describe_store_continuation_keeps_the_captured_collection_version() -> TestResult {
let mut fixture = Fixture::start()?;
fixture.seed()?;
collection(&mut fixture, "aux")?;
let first = call(
&json!({"selection":{"branch":"main","version":null},"page":{"rows":1,"bytes":65536},"after":{}}),
)?;
let answer = dispatch(&mut fixture.sender, &first)?;
assert!(!answer.is_error, "{}", answer.structured_content.get());
let first: types::DescribeStoreOutput = serde_json::from_str(answer.structured_content.get())?;
assert_eq!(first.collections.entries.len(), 1);
assert_eq!(first.collections.entries[0].name, "aux");
let cursor = first
.collections
.next_after
.ok_or_else(|| io::Error::other("collection cursor missing"))?;
collection(&mut fixture, "later")?;
let next = call(
&json!({"selection":{"branch":"main","version":first.selection.version},"page":{"rows":1,"bytes":65536},"after":{"collections":cursor}}),
)?;
let answer = dispatch(&mut fixture.sender, &next)?;
assert!(!answer.is_error, "{}", answer.structured_content.get());
let next: types::DescribeStoreOutput = serde_json::from_str(answer.structured_content.get())?;
assert_eq!(next.selection.version.0, first.selection.version.0);
assert_eq!(next.collections.entries.len(), 1);
assert_eq!(next.collections.entries[0].name, "records");
assert_eq!(next.collections.next_after, None);
assert_eq!(next.counts.collections, 2);
fixture.finish()
}