use dpcs::{
evaluate, evaluate_many, evaluate_requirements, parse_yaml_file, plan, try_plan,
validate_profile, CapabilityProfile, CapabilityResult, PlanResult,
};
fn fixture(path: &str) -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(path)
}
#[test]
fn matching_profile_satisfies_execution_model_plan() {
let contract = parse_yaml_file(fixture("valid/with_execution_model.dpcs.yaml")).unwrap();
let PlanResult::Ok(planned) = plan(&contract) else {
panic!("expected plan");
};
assert_eq!(planned.dpcs_version, "1.0.0");
let profile =
CapabilityProfile::from_yaml_file(fixture("capabilities/valid/matching.profile.yaml"))
.unwrap();
let CapabilityResult::Ok(report) = evaluate(&planned, &profile) else {
panic!("expected capability match");
};
assert_eq!(report.profile_identity, "reference.batch");
assert!(report.satisfied.iter().any(|id| id == "batch.compute"));
assert!(report.satisfied.iter().any(|id| id == "sql.readwrite"));
assert!(report.missing_mandatory.is_empty());
assert!(report
.unsupported_optional
.iter()
.any(|id| id == "observability.metrics"));
}
#[test]
fn missing_mandatory_capability_is_rejected() {
let contract = parse_yaml_file(fixture("valid/with_execution_model.dpcs.yaml")).unwrap();
let planned = try_plan(&contract).expect("plan");
let profile = CapabilityProfile::from_yaml_file(fixture(
"capabilities/invalid/missing_mandatory.profile.yaml",
))
.unwrap();
let CapabilityResult::Err {
report,
diagnostics,
} = evaluate(&planned, &profile)
else {
panic!("expected capability failure");
};
assert_eq!(report.missing_mandatory, vec!["sql.readwrite".to_owned()]);
assert!(diagnostics
.diagnostics
.iter()
.any(|d| d.id == "DPCS-CAP-005"));
assert!(diagnostics.diagnostics.iter().any(|d| {
d.id == "DPCS-CAP-005"
&& d.object_ref.as_deref()
== Some("execution.externalDependencies.capability:sql.readwrite")
}));
assert!(diagnostics
.diagnostics
.iter()
.any(|d| d.stage.to_string() == "capabilityEvaluation"));
}
#[test]
fn validate_profile_rejects_empty_and_duplicate_ids() {
let profile =
CapabilityProfile::from_yaml_file(fixture("capabilities/invalid/bad_profile.profile.yaml"))
.unwrap();
let report = validate_profile(&profile);
assert!(report.diagnostics.iter().any(|d| d.id == "DPCS-CAP-001"));
assert!(report.diagnostics.iter().any(|d| d.id == "DPCS-CAP-002"));
}
#[test]
fn evaluate_requirements_matches_without_plan() {
let contract = parse_yaml_file(fixture("valid/with_execution_model.dpcs.yaml")).unwrap();
let requirements = contract.execution.as_ref().expect("execution");
let profile =
CapabilityProfile::from_yaml_file(fixture("capabilities/valid/matching.profile.yaml"))
.unwrap();
assert!(evaluate_requirements(requirements, &profile).is_ok());
}
#[test]
fn evaluate_many_ranks_profiles() {
let contract = parse_yaml_file(fixture("valid/with_execution_model.dpcs.yaml")).unwrap();
let planned = try_plan(&contract).expect("plan");
let matching =
CapabilityProfile::from_yaml_file(fixture("capabilities/valid/matching.profile.yaml"))
.unwrap();
let incomplete = CapabilityProfile::from_yaml_file(fixture(
"capabilities/invalid/missing_mandatory.profile.yaml",
))
.unwrap();
let invalid = CapabilityProfile::from_yaml_str(
r#"
identity: ""
dpcsVersion: ""
capabilities: []
"#,
)
.unwrap();
let results = evaluate_many(&planned, &[incomplete, invalid, matching]);
assert_eq!(results.len(), 3);
assert_eq!(results[0].0, "reference.batch");
assert!(results[0].1.is_ok());
assert_eq!(results[1].0, "incomplete.batch");
assert!(!results[1].1.is_ok());
assert!(results[2].0.trim().is_empty());
assert!(!results[2].1.is_ok());
}
#[test]
fn version_mismatch_warns_but_can_still_match() {
let contract = parse_yaml_file(fixture("valid/with_execution_model.dpcs.yaml")).unwrap();
let planned = try_plan(&contract).expect("plan");
let profile = CapabilityProfile::from_yaml_file(fixture(
"capabilities/valid/version_mismatch.profile.yaml",
))
.unwrap();
let CapabilityResult::Ok(report) = evaluate(&planned, &profile) else {
panic!("expected match with warning");
};
assert!(report.diagnostics.iter().any(|d| d.id == "DPCS-CAP-006"));
assert!(report
.diagnostics
.iter()
.any(|d| { d.id == "DPCS-CAP-006" && d.message.contains("plan/contract dpcsVersion") }));
}
#[test]
fn profile_round_trip_preserves_com() {
let profile =
CapabilityProfile::from_yaml_file(fixture("capabilities/valid/matching.profile.yaml"))
.unwrap();
let yaml = profile.to_yaml_str().unwrap();
let reparsed = CapabilityProfile::from_yaml_str(&yaml).unwrap();
assert_eq!(profile, reparsed);
let json = profile.to_json_str().unwrap();
assert_eq!(profile, CapabilityProfile::from_json_str(&json).unwrap());
}
#[test]
fn legacy_profile_alias_deserializes_as_identity() {
let profile = CapabilityProfile::from_yaml_str(
r#"
profile: "legacy.name"
dpcsVersion: "1.0.0-draft"
capabilities:
- id: "batch.compute"
"#,
)
.unwrap();
assert_eq!(profile.identity, "legacy.name");
}
#[test]
fn legacy_stub_yaml_accepts_string_capabilities() {
let profile = CapabilityProfile::from_yaml_str(
r#"
profile: "airflow"
capabilities:
- "batch.compute"
- "sql.readwrite"
"#,
)
.unwrap();
assert_eq!(profile.identity, "airflow");
assert!(profile.dpcs_version.is_empty());
assert_eq!(profile.capabilities.len(), 2);
assert_eq!(profile.capabilities[0].id, "batch.compute");
let report = validate_profile(&profile);
assert!(report.diagnostics.iter().any(|d| d.id == "DPCS-CAP-004"));
}
#[test]
fn software_capabilities_are_not_orchestrator_demand() {
let contract = parse_yaml_file(fixture("valid/with_execution_model.dpcs.yaml")).unwrap();
let mut requirements = contract.execution.expect("execution");
requirements.required_capabilities = vec!["batch.compute".into()];
requirements.external_dependencies.clear();
requirements.environment = Some(dpcs::ExecutionEnvironment {
software_capabilities: vec!["python3".into()],
..Default::default()
});
let profile = CapabilityProfile::from_yaml_str(
r#"
identity: "env-check"
dpcsVersion: "1.0.0-draft"
capabilities:
- id: "batch.compute"
"#,
)
.unwrap();
let CapabilityResult::Ok(report) = evaluate_requirements(&requirements, &profile) else {
panic!("environment softwareCapabilities must not become CAP-005 demand");
};
assert_eq!(report.satisfied, vec!["batch.compute".to_owned()]);
assert!(report.missing_mandatory.is_empty());
}