#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use exo_api::dagdb;
use jsonschema::{Draft, JSONSchema};
use serde_json::Value;
const OPENAPI_JSON: &str = include_str!("../../../docs/dagdb/api/openapi.json");
const FIXTURES_JSON: &str =
include_str!("../../exo-dag-db-api/fixtures/json/all_dto_fixtures.json");
fn spec() -> Value {
serde_json::from_str(OPENAPI_JSON).expect("parse docs/dagdb/api/openapi.json")
}
fn fixtures() -> Value {
serde_json::from_str(FIXTURES_JSON).expect("parse all_dto_fixtures.json")
}
fn compile_component(spec: &Value, schema_name: &str) -> JSONSchema {
let components = spec.get("components").expect("spec has components").clone();
assert!(
components
.get("schemas")
.and_then(|schemas| schemas.get(schema_name))
.is_some(),
"spec is missing component schema {schema_name}"
);
let root = serde_json::json!({
"$ref": format!("#/components/schemas/{schema_name}"),
"components": components,
});
JSONSchema::options()
.with_draft(Draft::Draft202012)
.compile(&root)
.unwrap_or_else(|err| panic!("compile component {schema_name}: {err}"))
}
fn assert_fixture_validates(
spec: &Value,
fixtures: &Value,
section: &str,
name: &str,
schema: &str,
) {
let instance = fixtures
.get(section)
.and_then(|section| section.get(name))
.unwrap_or_else(|| panic!("missing fixture {section}.{name}"));
let validator = compile_component(spec, schema);
if let Err(errors) = validator.validate(instance) {
let messages: Vec<String> = errors
.map(|error| format!("{} at {}", error, error.instance_path))
.collect();
panic!(
"fixture {section}.{name} does not validate against spec schema {schema}:\n{}",
messages.join("\n")
);
}
}
fn response_contract() -> Vec<(&'static str, &'static str, &'static str)> {
vec![
(
"DagDbIntakeResponse",
"intake",
dagdb::DAGDB_INTAKE_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbRouteResponse",
"route",
dagdb::DAGDB_ROUTE_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbContextPacketResponse",
"context_packet",
dagdb::DAGDB_CONTEXT_PACKET_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbValidateResponse",
"validate",
dagdb::DAGDB_VALIDATE_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbWritebackResponse",
"writeback",
dagdb::DAGDB_WRITEBACK_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbImportResponse",
"import",
dagdb::DAGDB_IMPORT_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbExportResponse",
"export",
dagdb::DAGDB_EXPORT_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbTrustCheckResponse",
"trust_check",
dagdb::DAGDB_TRUST_CHECK_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbCouncilDecisionResponse",
"council_decision",
dagdb::DAGDB_COUNCIL_DECISION_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbReceiptLookupResponse",
"receipt_lookup",
dagdb::DAGDB_RECEIPT_LOOKUP_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbCatalogLookupResponse",
"catalog_lookup",
dagdb::DAGDB_CATALOG_LOOKUP_RESPONSE_SCHEMA_VERSION,
),
(
"DagDbRouteLookupResponse",
"route_lookup",
dagdb::DAGDB_ROUTE_LOOKUP_RESPONSE_SCHEMA_VERSION,
),
]
}
#[test]
fn openapi_doc_parses_and_declares_every_live_route() {
let spec = spec();
assert_eq!(spec.get("openapi").and_then(Value::as_str), Some("3.1.0"));
let paths = spec.get("paths").expect("spec has paths");
for route in [
"/route",
"/context-packet",
"/writeback",
"/import",
"/export",
] {
assert!(paths.get(route).is_some(), "spec is missing path {route}");
}
}
#[test]
fn every_response_fixture_validates_against_spec_and_versions_agree() {
let spec = spec();
let fixtures = fixtures();
for (schema, fixture_name, rust_const) in response_contract() {
assert_fixture_validates(&spec, &fixtures, "responses", fixture_name, schema);
let spec_const = spec
.pointer(&format!(
"/components/schemas/{schema}/properties/schema_version/const"
))
.and_then(Value::as_str)
.unwrap_or_else(|| {
panic!("spec schema {schema} is missing properties.schema_version.const")
});
assert_eq!(
spec_const, rust_const,
"spec schema_version const for {schema} ({spec_const}) does not match Rust constant ({rust_const})"
);
let fixture_version = fixtures
.pointer(&format!("/responses/{fixture_name}/schema_version"))
.and_then(Value::as_str)
.unwrap_or_else(|| panic!("response fixture {fixture_name} is missing schema_version"));
assert_eq!(
fixture_version, rust_const,
"response fixture {fixture_name} schema_version ({fixture_version}) does not match Rust constant ({rust_const})"
);
}
}
#[test]
fn every_request_and_error_fixture_validates_against_spec() {
let spec = spec();
let fixtures = fixtures();
let requests: &[(&str, &str)] = &[
("intake", "DagDbIntakeRequest"),
("route", "DagDbRouteRequest"),
("context_packet", "DagDbContextPacketRequest"),
("validate", "DagDbValidateRequest"),
("writeback", "DagDbWritebackRequest"),
("trust_check", "DagDbTrustCheckRequest"),
("council_decision", "DagDbCouncilDecisionRequest"),
("receipt_lookup", "DagDbReceiptLookupRequest"),
("catalog_lookup", "DagDbCatalogLookupRequest"),
("route_lookup", "DagDbRouteLookupRequest"),
];
for (fixture_name, schema) in requests {
assert_fixture_validates(&spec, &fixtures, "requests", fixture_name, schema);
}
assert_fixture_validates(
&spec,
&fixtures,
"errors",
"tenant_scope_mismatch",
"DagDbErrorEnvelope",
);
}