ModelConnector/crates/model-connector/tests/published_schemas.rs
Meta Vens 06a47bb13f connector-usage-only: delete the dollar authorities (WIP, does not yet build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:43:18 +01:00

117 lines
4.4 KiB
Rust

//! `schemas/` at the repository root publishes one JSON Schema per v4 document,
//! generated from the types. This test regenerates them and fails on any
//! difference; run it with `MODEL_CONNECTOR_REGENERATE_SCHEMAS=1` to rewrite the
//! files from the types.
use std::path::{Path, PathBuf};
use model_connector::*;
use schemars::{JsonSchema, schema_for};
const REGENERATE: &str = "MODEL_CONNECTOR_REGENERATE_SCHEMAS";
fn render<T: JsonSchema>() -> String {
let mut text = serde_json::to_string_pretty(&schema_for!(T)).unwrap();
text.push('\n');
text
}
fn schemas_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../schemas")
}
#[test]
fn published_schemas_are_the_types() {
let documents = [
("provider_request", render::<ProviderRequest>()),
("transport_invocation", render::<TransportInvocation>()),
("transport_result", render::<TransportResult>()),
("transport_receipt", render::<TransportReceipt>()),
("transport_envelope", render::<TransportEnvelope>()),
("usage_readout", render::<UsageReadout>()),
];
let dir = schemas_dir();
if std::env::var_os(REGENERATE).is_some() {
std::fs::create_dir_all(&dir).unwrap();
for (name, text) in &documents {
std::fs::write(dir.join(format!("{name}.json")), text).unwrap();
}
}
for (name, text) in &documents {
let committed = std::fs::read_to_string(dir.join(format!("{name}.json")))
.unwrap_or_else(|_| panic!("schemas/{name}.json is missing"));
assert_eq!(&committed, text, "schemas/{name}.json is not the type's");
}
let mut on_disk: Vec<String> = std::fs::read_dir(&dir)
.unwrap()
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
.collect();
on_disk.sort();
let mut expected: Vec<String> = documents
.iter()
.map(|(name, _)| format!("{name}.json"))
.collect();
expected.sort();
assert_eq!(
on_disk, expected,
"schemas/ holds exactly the six documents"
);
}
#[test]
fn a_published_schema_refuses_an_unknown_field() {
let schema = serde_json::to_value(schema_for!(ProviderRequest)).unwrap();
assert_eq!(schema["additionalProperties"], serde_json::json!(false));
let required = schema["required"].as_array().unwrap();
assert!(required.contains(&serde_json::json!("max_output_tokens")));
}
/// The schema and the wire agree on the ciphertext: the schema says binary, not
/// an array of integers, and the encoder writes MessagePack `bin`.
#[test]
fn the_published_ciphertext_is_what_the_wire_sends() {
let schema = serde_json::to_value(schema_for!(TransportEnvelope)).unwrap();
let ciphertext = &schema["properties"]["ciphertext"];
assert_eq!(ciphertext["type"], "string");
assert_eq!(ciphertext["format"], "binary");
assert!(ciphertext.get("items").is_none());
let published = std::fs::read_to_string(schemas_dir().join("transport_envelope.json")).unwrap();
let published: serde_json::Value = serde_json::from_str(&published).unwrap();
assert_eq!(&published["properties"]["ciphertext"], ciphertext);
let envelope = TransportEnvelope {
schema_id: "gamecult.model.transport_envelope.v4".to_string(),
caller_runtime_id: "caller".to_string(),
request_id: "request".to_string(),
message_kind: EnvelopeKind::Invocation,
nonce: [1; 12],
ciphertext: vec![9; 40],
};
let sent =
rmpv::decode::read_value(&mut rmp_serde::to_vec_named(&envelope).unwrap().as_slice())
.unwrap();
let rmpv::Value::Map(entries) = sent else {
panic!("the envelope is a named map");
};
let (_, wire) = entries
.iter()
.find(|(key, _)| key.as_str() == Some("ciphertext"))
.unwrap();
assert!(matches!(wire, rmpv::Value::Binary(bytes) if bytes.len() == 40));
}
#[test]
fn the_refusal_schema_is_one_flat_string_enum() {
let schema: serde_json::Value = serde_json::from_str(&render::<Refusal>()).unwrap();
assert!(schema.get("oneOf").is_none(), "{schema}");
assert_eq!(schema["type"], "string");
let variants: Vec<&str> = schema["enum"]
.as_array()
.expect("a closed set of strings")
.iter()
.map(|value| value.as_str().unwrap())
.collect();
assert_eq!(variants.len(), 13);
assert!(variants.contains(&"ForeignReasoningState"));
assert!(variants.contains(&"ProviderRejected"));
}