acorn-schema 0.4.0

Portable ACORN schema, validation, and codecs
use crate::standard::ocp::{Contribution, ContributionId, ManagementProfileClaim, Project, Recognition, RecognitionProgram};
use crate::standard::{ClaimAssessment, ConformanceResult, ContributionKind, StandardReference, StandardsClaim, StandardsRelationship};
use crate::validation::Validate;

fn reference(identifier: &str) -> StandardReference {
    StandardReference::init()
        .authority("Open Compute Project")
        .identifier(identifier)
        .canonical_uri(format!("https://www.opencompute.org/contributions/{identifier}"))
        .build()
}
#[test]
fn test_management_profile_claim_requires_test_evidence() {
    [ConformanceResult::Fail, ConformanceResult::Partial, ConformanceResult::Pass]
        .into_iter()
        .for_each(|result| {
            let claim = ManagementProfileClaim::init()
                .profile(reference("hardware-management-profile"))
                .underlying_standard(reference("redfish"))
                .result(result)
                .build();
            let report = claim.validate().expect_err("tested result should require a timestamp and evidence");
            let paths = report.iter().map(|issue| issue.path.as_str()).collect::<Vec<_>>();
            assert_eq!(paths, ["tested_at", "evidence_uri"]);
        });
    let not_tested = ManagementProfileClaim::init()
        .profile(reference("hardware-management-profile"))
        .underlying_standard(reference("redfish"))
        .result(ConformanceResult::NotTested)
        .build();
    assert!(not_tested.validate().is_ok());
    let passed = ManagementProfileClaim::init()
        .profile(reference("hardware-management-profile"))
        .underlying_standard(reference("redfish"))
        .tested_at("2026-09-18T12:30:00-04:00")
        .result(ConformanceResult::Pass)
        .evidence_uri("https://example.org/reports/ocp-profile")
        .build();
    assert!(passed.validate().is_ok());
}
#[test]
fn test_ocp_contribution_fixture_projects_reference_without_claim_inference() {
    let content = include_str!("../../../../../tests/fixtures/schema/ocp/contribution.json");
    let contribution: Contribution = serde_json::from_str(content).expect("OCP contribution fixture should deserialize");
    assert!(contribution.validate().is_ok());
    let reference = StandardReference::from(&contribution);
    assert_eq!(reference.authority, "Open Compute Project");
    assert_eq!(reference.identifier, "S0806");
    assert_eq!(reference.title.as_deref(), Some("Hardware Management Profile"));
    assert_eq!(reference.version.as_deref(), Some("1.0"));
    assert_eq!(reference.canonical_uri, contribution.canonical_uri);
    let yaml = serde_norway::to_string(&contribution).expect("OCP contribution should serialize as YAML");
    let round_trip: Contribution = serde_norway::from_str(&yaml).expect("OCP contribution YAML should deserialize");
    assert_eq!(round_trip, contribution);
}
#[test]
fn test_ocp_contribution_identifier_and_open_values_validate() {
    assert!(ContributionId::from("S0806").validate().is_ok());
    assert!(ContributionId::from("0806").validate().is_err());
    assert!(ContributionId::from("S").validate().is_err());
    assert!(Project::from("Hardware Management").validate().is_ok());
    assert!(Project::from("  ").validate().is_err());
    assert!(ContributionKind::Other("Firmware bundle".to_string()).validate().is_ok());
    assert!(ContributionKind::Other("".to_string()).validate().is_err());
    assert!(RecognitionProgram::Other("Future program".to_string()).validate().is_ok());
    assert!(RecognitionProgram::Other("".to_string()).validate().is_err());
    assert_eq!(
        serde_json::from_str::<ContributionKind>(r#""firmwareBundle""#).expect("unknown contribution kind should deserialize"),
        ContributionKind::Other("firmwareBundle".to_string())
    );
    assert_eq!(
        serde_json::from_str::<RecognitionProgram>(r#""futureProgram""#).expect("unknown recognition program should deserialize"),
        RecognitionProgram::Other("futureProgram".to_string())
    );
}
#[test]
fn test_ocp_recognition_validates_source_and_timestamp_order() {
    let valid = Recognition::init()
        .program(RecognitionProgram::Accepted)
        .subject_identifier("product-1")
        .source_uri("https://www.opencompute.org/products/product-1")
        .awarded_at("2026-01-01T00:00:00Z")
        .expires_at("2027-01-01T00:00:00Z")
        .build();
    assert!(valid.validate().is_ok());
    let invalid_source = Recognition {
        source_uri: "urn:ocp:product-1".to_string(),
        ..valid.clone()
    };
    assert!(invalid_source.validate().is_err());
    let invalid_order = Recognition {
        expires_at: Some("2025-01-01T00:00:00Z".to_string()),
        ..valid
    };
    let report = invalid_order.validate().expect_err("expiration must follow award");
    assert!(report
        .iter()
        .any(|issue| issue.path == "expires_at" && issue.error.code == "timestamp_order"));
}
#[test]
fn test_standard_reference_and_verified_claim_require_http_evidence() {
    let invalid = StandardReference {
        canonical_uri: "urn:ocp:S0806".to_string(),
        ..reference("S0806")
    };
    assert!(invalid.validate().is_err());
    let claim = StandardsClaim::init()
        .standard(reference("S0806"))
        .relationship(StandardsRelationship::ConformsTo)
        .assessment(ClaimAssessment::Verified)
        .build();
    let report = claim.validate().expect_err("verified claim should require evidence");
    assert!(report
        .iter()
        .any(|issue| issue.path == "evidence_uri" && issue.error.code == "evidence_required"));
    let claim = StandardsClaim {
        evidence_uri: Some("https://example.org/evidence/S0806".to_string()),
        ..claim
    };
    assert!(claim.validate().is_ok());
}