use std::error::Error;
use lodestone_client::{
CapabilityGrant, Command, Digest, DiscoverResult, Identity, LODESTONE_API_COMMAND_OUTCOMES,
LODESTONE_API_SCHEMA_ID, LODESTONE_API_SPEC_SHA256, RequestBody, Resource, ResultBody, bulk_batch_id,
describe_tool_operation, load_tool_pack, request_command,
};
use serde_json::{Value, json};
const EXAMPLES: &str = include_str!("../../../clients/native/generated/lodestone-api-examples.json");
const EXECUTABLE_EXAMPLES: &str =
include_str!("../../../clients/native/generated/lodestone-api-executable-examples.json");
const FIXTURES: &str = include_str!("../../../tests/p10/lodestone-api-closed-schema.json");
const BULK_BATCH_ID_VECTORS: &str = include_str!("../../../clients/native/fixtures/lodestone-bulk-batch-id.json");
#[test]
fn public_client_derives_the_language_neutral_bulk_batch_vectors() -> Result<(), Box<dyn Error>> {
let document: Value = serde_json::from_str(BULK_BATCH_ID_VECTORS)?;
let vectors = document
.get("vectors")
.and_then(Value::as_array)
.ok_or("bulk batch fixture omitted vectors")?;
for vector in vectors {
let collection = Identity {
value: vector
.get("collection_identity")
.and_then(Value::as_str)
.ok_or("bulk batch vector omitted collection")?
.to_owned(),
};
let index: Resource =
serde_json::from_value(vector.get("index").cloned().ok_or("bulk batch vector omitted index")?)?;
let actions = vector
.get("item_action_ids")
.and_then(Value::as_array)
.ok_or("bulk batch vector omitted actions")?
.iter()
.map(|value| {
value
.as_str()
.map(|value| Digest {
value: value.to_owned(),
})
.ok_or("bulk batch vector action is not a string")
})
.collect::<Result<Vec<_>, _>>()?;
let expected = vector
.get("batch_id")
.and_then(Value::as_str)
.ok_or("bulk batch vector omitted result")?;
assert_eq!(bulk_batch_id(&collection, &index, &actions)?.value, expected);
}
Ok(())
}
#[test]
fn every_generated_example_decodes_and_reencodes_through_the_closed_union() -> Result<(), Box<dyn Error>> {
let document: Value = serde_json::from_str(EXAMPLES)?;
let commands = document
.get("commands")
.and_then(Value::as_object)
.ok_or("generated examples omitted commands")?;
assert_eq!(commands.len(), lodestone_client::RUST_CLIENT_METHOD_COUNT);
for (name, example) in commands {
let request_value = json!({
"command": name,
"body": example.get("request_body").ok_or("example omitted request_body")?,
});
let request: RequestBody = serde_json::from_value(request_value.clone())?;
assert_eq!(serde_json::to_value(&request)?, request_value);
assert_eq!(
serde_json::to_value(request_command(&request))?,
Value::String(name.clone())
);
let result_value = json!({
"command": name,
"body": example.get("expected_result").ok_or("example omitted expected_result")?,
});
let result: ResultBody = serde_json::from_value(result_value.clone())?;
assert_eq!(serde_json::to_value(result)?, result_value);
}
assert_eq!(
document
.pointer("/raw_json_text/wire/bytes_base64")
.and_then(Value::as_str),
Some("eyJuIjoxLjIzMDAsIm4iOjJ9")
);
Ok(())
}
#[test]
fn connected_tool_loader_intersects_exact_discovery_and_result_ceiling() -> Result<(), Box<dyn Error>> {
let examples: Value = serde_json::from_str(EXAMPLES)?;
let mut discovery: DiscoverResult = serde_json::from_value(
examples
.pointer("/commands/discover/expected_result")
.cloned()
.ok_or("discover example missing")?,
)?;
discovery.schema_id = LODESTONE_API_SCHEMA_ID.to_owned();
discovery.schema_digest.value = LODESTONE_API_SPEC_SHA256.to_owned();
let fixtures: Value = serde_json::from_str(FIXTURES)?;
let mut grant: CapabilityGrant = serde_json::from_value(
fixtures
.pointer("/named_types/capability_grant")
.cloned()
.ok_or("capability fixture missing")?,
)?;
grant.command = "search".to_owned();
grant.state = "available".to_owned();
grant.capability_family = "data".to_owned();
discovery.capabilities.push(grant);
let loaded = load_tool_pack(&discovery, "data", 2 * 1024 * 1024)?;
assert_eq!(loaded["operations"].as_array().map(Vec::len), Some(1));
let omitted = load_tool_pack(&discovery, "data", 1)?;
assert_eq!(omitted["operations"].as_array().map(Vec::len), Some(0));
discovery.schema_digest.value = "0".repeat(64);
assert!(load_tool_pack(&discovery, "data", 2 * 1024 * 1024).is_err());
let descriptor = describe_tool_operation("search")?.ok_or("search tool descriptor missing")?;
assert_eq!(descriptor.get("command").and_then(Value::as_str), Some("search"));
assert!(descriptor.get("$defs").and_then(Value::as_object).is_some());
assert_internal_references_resolve(&descriptor, &descriptor)?;
assert!(describe_tool_operation("activate_service_credential")?.is_none());
Ok(())
}
fn assert_internal_references_resolve(root: &Value, value: &Value) -> Result<(), Box<dyn Error>> {
match value {
Value::Object(object) => {
if let Some(reference) = object.get("$ref").and_then(Value::as_str) {
let pointer = reference.strip_prefix('#').ok_or("external schema reference")?;
if root.pointer(pointer).is_none() {
return Err(format!("unresolved schema reference: {reference}").into());
}
}
for child in object.values() {
assert_internal_references_resolve(root, child)?;
}
}
Value::Array(values) => {
for child in values {
assert_internal_references_resolve(root, child)?;
}
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
Ok(())
}
#[test]
fn executable_templates_freshen_and_cover_recovery_exact_number_and_raw_json() -> Result<(), Box<dyn Error>> {
let document: Value = serde_json::from_str(EXECUTABLE_EXAMPLES)?;
let templates = document
.get("templates")
.and_then(Value::as_object)
.ok_or("executable examples omitted templates")?;
assert_eq!(templates.len(), 6);
for (ordinal, (name, template)) in templates.iter().enumerate() {
let command = template
.get("command")
.and_then(Value::as_str)
.ok_or("executable template omitted command")?;
let mut body = template
.get("request_body")
.cloned()
.ok_or("executable template omitted request body")?;
for pointer in template
.get("freshen")
.and_then(Value::as_array)
.ok_or("executable template omitted freshness paths")?
{
let pointer = pointer.as_str().ok_or("freshness path is not text")?;
let value = body.pointer_mut(pointer).ok_or("freshness path does not resolve")?;
*value = if pointer.ends_with("incarnation") {
Value::String(format!("{:032x}", ordinal + 1))
} else if pointer.ends_with("digest") {
Value::String(format!("{:064x}", ordinal + 1))
} else {
Value::String(format!("ec1-fresh-{ordinal}"))
};
}
let request_value = json!({"command": command, "body": body});
let request: RequestBody = serde_json::from_value(request_value.clone())?;
assert_eq!(serde_json::to_value(&request)?, request_value, "{name}");
}
assert_eq!(
document.pointer("/templates/exact_number/request_body/window_end"),
Some(&Value::String("18446744073709551615".to_owned()))
);
assert_eq!(
document.pointer("/templates/raw_json/request_body/source/bytes_base64"),
Some(&Value::String("e30=".to_owned()))
);
let submit = LODESTONE_API_COMMAND_OUTCOMES
.iter()
.find(|outcome| outcome.command == Command::SubmitDocumentMutation)
.ok_or("submit mutation outcome contract missing")?;
assert!(submit.possible_submission_states.contains(&"indeterminate"));
assert_eq!(submit.resolver_command, Some(Command::ResolveDocumentMutation));
Ok(())
}