use serde_json::Value;
use std::fs;
mod common;
#[test]
fn package_manifest_example_uses_declared_schema_version() {
let manifest: Value = serde_json::from_str(
&fs::read_to_string(common::repo_root().join("examples/protein-package/manifest.json"))
.expect("read package manifest"),
)
.expect("manifest JSON");
assert_eq!(manifest["schema_version"], "biors.package.v1");
assert_eq!(manifest["package_layout"]["models"], "models");
assert_eq!(manifest["package_layout"]["docs"], "docs");
assert_eq!(manifest["metadata"]["license"]["expression"], "CC0-1.0");
assert_eq!(
manifest["metadata"]["model_card"]["path"],
"docs/model-card.md"
);
assert!(manifest["model"]["checksum"].is_string());
assert!(manifest["tokenizer"]["checksum"].is_string());
assert!(manifest["vocab"]["checksum"].is_string());
assert!(manifest["metadata"]["license"]["file"]["checksum"].is_string());
assert!(manifest["metadata"]["citation"]["file"]["checksum"].is_string());
assert!(manifest["metadata"]["model_card"]["checksum"].is_string());
assert!(manifest["expected_input"]["dtype"].is_string());
assert!(manifest["fixtures"][0]["input_hash"]
.as_str()
.expect("fixture input hash")
.starts_with("sha256:"));
assert!(manifest["fixtures"][0]["expected_output_hash"]
.as_str()
.expect("fixture output hash")
.starts_with("sha256:"));
common::assert_json_value_matches_schema(&manifest, "schemas/package-manifest.v1.json");
}
#[test]
fn package_manifest_schemas_reject_empty_contract_identifiers() {
let mut v1_manifest: Value = serde_json::from_str(
&fs::read_to_string(common::repo_root().join("examples/protein-package/manifest.json"))
.expect("read package manifest"),
)
.expect("manifest JSON");
v1_manifest["tokenizer"]["name"] = Value::String(String::new());
v1_manifest["tokenizer"]["contract_version"] = Value::String(String::new());
v1_manifest["vocab"]["name"] = Value::String(String::new());
v1_manifest["vocab"]["contract_version"] = Value::String(String::new());
v1_manifest["preprocessing"][0]["name"] = Value::String(String::new());
v1_manifest["preprocessing"][0]["implementation"] = Value::String(String::new());
v1_manifest["preprocessing"][0]["contract"] = Value::String(String::new());
v1_manifest["preprocessing"][0]["contract_version"] = Value::String(String::new());
common::assert_payload_rejected_by_schema(&v1_manifest, "schemas/package-manifest.v1.json");
let mut v0_manifest = v1_manifest;
v0_manifest["schema_version"] = Value::String("biors.package.v0".into());
if let Some(object) = v0_manifest.as_object_mut() {
object.remove("package_layout");
object.remove("metadata");
if let Some(runtime) = object.get_mut("runtime").and_then(Value::as_object_mut) {
runtime.remove("version");
}
}
common::assert_payload_rejected_by_schema(&v0_manifest, "schemas/package-manifest.v0.json");
}
#[test]
fn package_manifest_schemas_reject_empty_fixture_names_and_shapes() {
let mut v1_manifest: Value = serde_json::from_str(
&fs::read_to_string(common::repo_root().join("examples/protein-package/manifest.json"))
.expect("read package manifest"),
)
.expect("manifest JSON");
v1_manifest["fixtures"][0]["name"] = Value::String(String::new());
v1_manifest["expected_input"]["shape"] = Value::Array(vec![]);
v1_manifest["expected_output"]["shape"] = Value::Array(vec![]);
common::assert_payload_rejected_by_schema(&v1_manifest, "schemas/package-manifest.v1.json");
let mut v1_empty_dimension = v1_manifest.clone();
v1_empty_dimension["fixtures"][0]["name"] = Value::String("tiny".into());
v1_empty_dimension["expected_input"]["shape"] = Value::Array(vec![
Value::String(String::new()),
Value::String("256".into()),
]);
v1_empty_dimension["expected_output"]["shape"] = Value::Array(vec![Value::String(" ".into())]);
common::assert_payload_rejected_by_schema(
&v1_empty_dimension,
"schemas/package-manifest.v1.json",
);
let mut v0_manifest = v1_manifest;
v0_manifest["schema_version"] = Value::String("biors.package.v0".into());
if let Some(object) = v0_manifest.as_object_mut() {
object.remove("package_layout");
object.remove("metadata");
if let Some(runtime) = object.get_mut("runtime").and_then(Value::as_object_mut) {
runtime.remove("version");
}
}
common::assert_payload_rejected_by_schema(&v0_manifest, "schemas/package-manifest.v0.json");
let mut v0_empty_dimension = v1_empty_dimension;
v0_empty_dimension["schema_version"] = Value::String("biors.package.v0".into());
if let Some(object) = v0_empty_dimension.as_object_mut() {
object.remove("package_layout");
object.remove("metadata");
if let Some(runtime) = object.get_mut("runtime").and_then(Value::as_object_mut) {
runtime.remove("version");
}
}
common::assert_payload_rejected_by_schema(
&v0_empty_dimension,
"schemas/package-manifest.v0.json",
);
}
#[test]
fn cli_outputs_match_package_schemas() {
let manifest = common::repo_root().join("examples/protein-package/manifest.json");
let observations = common::repo_root().join("examples/protein-package/observations.json");
let package_inspect = common::run_biors_paths(&["package", "inspect"], &[&manifest]).stdout;
common::assert_payload_matches_schema(
&package_inspect,
"schemas/package-inspect-output.v0.json",
);
let package_validate = common::run_biors_paths(&["package", "validate"], &[&manifest]).stdout;
common::assert_payload_matches_schema(
&package_validate,
"schemas/package-validation-report.v0.json",
);
let package_bridge = common::run_biors_paths(&["package", "bridge"], &[&manifest]).stdout;
common::assert_payload_matches_schema(&package_bridge, "schemas/package-bridge-output.v0.json");
let package_verify =
common::run_biors_paths(&["package", "verify"], &[&manifest, &observations]).stdout;
common::assert_payload_matches_schema(&package_verify, "schemas/package-verify-output.v0.json");
let temp = common::TempDir::new("schema-package-skeleton");
let project = temp.path().join("python-project");
fs::create_dir_all(&project).expect("create project");
fs::write(project.join("model.onnx"), b"onnx").expect("write model");
fs::write(
project.join("tokenizer_config.json"),
r#"{"tokenizer_class":"BertTokenizer","cls_token":"[CLS]","sep_token":"[SEP]"}"#,
)
.expect("write tokenizer config");
let fixture_input = temp.write("tiny.fasta", ">tiny\nACDE\n");
let fixture_output = temp.write("tiny.output.json", r#"{"ok":true}"#);
let output_dir = temp.path().join("package");
let skeleton = std::process::Command::new(env!("CARGO_BIN_EXE_biors"))
.arg("package")
.arg("convert-project")
.arg(&project)
.arg("--output")
.arg(&output_dir)
.arg("--name")
.arg("schema-package")
.arg("--license")
.arg("CC0-1.0")
.arg("--citation")
.arg("schema package fixture")
.arg("--model-card-summary")
.arg("Schema package fixture.")
.arg("--intended-use")
.arg("Schema validation")
.arg("--limitation")
.arg("Not for inference")
.arg("--fixture-input")
.arg(&fixture_input)
.arg("--fixture-output")
.arg(&fixture_output)
.output()
.expect("run package convert-project")
.stdout;
common::assert_payload_matches_schema(&skeleton, "schemas/package-skeleton-output.v0.json");
}
#[test]
fn package_rejection_examples_match_schemas() {
let mismatch_report = serde_json::json!({
"package": "protein-seed",
"fixtures": 1,
"passed": 0,
"failed": 1,
"observation_issues": [],
"results": [
{
"name": "tiny-protein",
"input_path": "fixtures/tiny.fasta",
"expected_output_path": "fixtures/tiny.output.json",
"observed_output_path": "observed/tiny.bad.json",
"expected_output_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"observed_output_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"status": "failed",
"checksum_mismatch": true,
"content_mismatch": true,
"issue_code": "output_content_mismatch",
"content_diff": {
"expected_path": "fixtures/tiny.output.json",
"observed_path": "observed/tiny.bad.json",
"expected_len": 32,
"observed_len": 28,
"first_difference": {
"byte_offset": 10,
"expected_byte": 34,
"observed_byte": 48
}
},
"issue": "output content mismatch between 'fixtures/tiny.output.json' and 'observed/tiny.bad.json'"
}
]
});
common::assert_json_value_matches_schema(
&mismatch_report,
"schemas/package-verify-output.v0.json",
);
}