Skip to content

Commit a7df924

Browse files
authored
Added unit tests for assertions (#16)
1 parent 1baea8b commit a7df924

File tree

1 file changed

+338
-0
lines changed

1 file changed

+338
-0
lines changed

src/assertion.rs

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,341 @@ fn validate_custom_assertion(custom: &CustomAssertion) -> Result<(), String> {
399399

400400
Ok(())
401401
}
402+
#[cfg(test)]
403+
mod assertion_tests {
404+
use super::*;
405+
use serde_json::json;
406+
407+
// Tests for DoNotTrainAssertion
408+
#[test]
409+
fn test_do_not_train_assertion_creation_and_validation() {
410+
// Valid DoNotTrainAssertion
411+
let valid_assertion =
412+
DoNotTrainAssertion::new("Contains copyrighted material".to_string(), true);
413+
assert!(valid_assertion.verify().is_ok());
414+
415+
// Invalid - empty reason
416+
let invalid_empty = DoNotTrainAssertion::new("".to_string(), true);
417+
assert!(invalid_empty.verify().is_err());
418+
assert_eq!(
419+
invalid_empty.verify().unwrap_err(),
420+
"[DoNotTrainAssertion] Missing required reason field"
421+
);
422+
423+
// Invalid - whitespace only reason
424+
let invalid_whitespace = DoNotTrainAssertion::new(" ".to_string(), true);
425+
assert!(invalid_whitespace.verify().is_err());
426+
427+
// Invalid - not enforced
428+
let invalid_not_enforced = DoNotTrainAssertion::new("Valid reason".to_string(), false);
429+
assert!(invalid_not_enforced.verify().is_err());
430+
assert_eq!(
431+
invalid_not_enforced.verify().unwrap_err(),
432+
"[DoNotTrainAssertion] Assertion must have enforced=true to be valid"
433+
);
434+
}
435+
436+
// Tests for ActionAssertion
437+
#[test]
438+
fn test_action_assertion_validation() {
439+
// Valid ActionAssertion
440+
let valid_action = ActionAssertion {
441+
actions: vec![Action {
442+
action: "c2pa.created".to_string(),
443+
software_agent: Some("TestAgent/1.0".to_string()),
444+
parameters: Some(json!({"key": "value"})),
445+
digital_source_type: Some("trained".to_string()),
446+
instance_id: Some("instance_123".to_string()),
447+
}],
448+
};
449+
let assertion = Assertion::Action(valid_action);
450+
assert!(validate_assertion(&assertion).is_ok());
451+
452+
// Invalid - empty actions vector
453+
let empty_actions = ActionAssertion { actions: vec![] };
454+
let assertion = Assertion::Action(empty_actions);
455+
assert!(validate_assertion(&assertion).is_err());
456+
457+
// Invalid - empty action name
458+
let invalid_action_name = ActionAssertion {
459+
actions: vec![Action {
460+
action: "".to_string(),
461+
software_agent: None,
462+
parameters: None,
463+
digital_source_type: None,
464+
instance_id: None,
465+
}],
466+
};
467+
let assertion = Assertion::Action(invalid_action_name);
468+
assert!(validate_assertion(&assertion).is_err());
469+
470+
// Invalid - empty software agent
471+
let invalid_software_agent = ActionAssertion {
472+
actions: vec![Action {
473+
action: "c2pa.created".to_string(),
474+
software_agent: Some("".to_string()),
475+
parameters: None,
476+
digital_source_type: None,
477+
instance_id: None,
478+
}],
479+
};
480+
let assertion = Assertion::Action(invalid_software_agent);
481+
assert!(validate_assertion(&assertion).is_err());
482+
483+
// Invalid - null parameters
484+
let invalid_null_params = ActionAssertion {
485+
actions: vec![Action {
486+
action: "c2pa.created".to_string(),
487+
software_agent: None,
488+
parameters: Some(serde_json::Value::Null),
489+
digital_source_type: None,
490+
instance_id: None,
491+
}],
492+
};
493+
let assertion = Assertion::Action(invalid_null_params);
494+
assert!(validate_assertion(&assertion).is_err());
495+
}
496+
497+
// Tests for HashAssertion
498+
#[test]
499+
fn test_hash_assertion_validation() {
500+
// Valid SHA-256 hash
501+
let valid_sha256 = HashAssertion {
502+
algorithm: "sha256".to_string(),
503+
hash_value: vec![0u8; 32], // 32 bytes for SHA-256
504+
};
505+
let assertion = Assertion::Hash(valid_sha256);
506+
assert!(validate_assertion(&assertion).is_ok());
507+
508+
// Valid SHA-384 hash
509+
let valid_sha384 = HashAssertion {
510+
algorithm: "sha384".to_string(),
511+
hash_value: vec![0u8; 48], // 48 bytes for SHA-384
512+
};
513+
let assertion = Assertion::Hash(valid_sha384);
514+
assert!(validate_assertion(&assertion).is_ok());
515+
516+
// Valid SHA-512 hash
517+
let valid_sha512 = HashAssertion {
518+
algorithm: "sha512".to_string(),
519+
hash_value: vec![0u8; 64], // 64 bytes for SHA-512
520+
};
521+
let assertion = Assertion::Hash(valid_sha512);
522+
assert!(validate_assertion(&assertion).is_ok());
523+
524+
// Invalid - empty algorithm
525+
let invalid_empty_alg = HashAssertion {
526+
algorithm: "".to_string(),
527+
hash_value: vec![0u8; 32],
528+
};
529+
let assertion = Assertion::Hash(invalid_empty_alg);
530+
assert!(validate_assertion(&assertion).is_err());
531+
532+
// Invalid - unsupported algorithm
533+
let invalid_alg = HashAssertion {
534+
algorithm: "md5".to_string(),
535+
hash_value: vec![0u8; 16],
536+
};
537+
let assertion = Assertion::Hash(invalid_alg);
538+
assert!(validate_assertion(&assertion).is_err());
539+
540+
// Invalid - wrong hash length for SHA-256
541+
let invalid_length = HashAssertion {
542+
algorithm: "sha256".to_string(),
543+
hash_value: vec![0u8; 16], // Wrong length
544+
};
545+
let assertion = Assertion::Hash(invalid_length);
546+
assert!(validate_assertion(&assertion).is_err());
547+
548+
// Invalid - empty hash value
549+
let invalid_empty_hash = HashAssertion {
550+
algorithm: "sha256".to_string(),
551+
hash_value: vec![],
552+
};
553+
let assertion = Assertion::Hash(invalid_empty_hash);
554+
assert!(validate_assertion(&assertion).is_err());
555+
}
556+
557+
// Tests for IngredientAssertion
558+
#[test]
559+
fn test_ingredient_assertion_validation() {
560+
// Valid IngredientAssertion
561+
let valid_ingredient = IngredientAssertion {
562+
title: "Training Dataset".to_string(),
563+
relationship: "inputTo".to_string(),
564+
format: "application/zip".to_string(),
565+
document_id: "doc_123".to_string(),
566+
instance_id: "instance_123".to_string(),
567+
data: IngredientData {
568+
url: "https://example.com/dataset.zip".to_string(),
569+
alg: "sha256".to_string(),
570+
hash: "abc123".to_string(),
571+
data_types: vec!["dataset".to_string()],
572+
},
573+
};
574+
let assertion = Assertion::Ingredient(valid_ingredient.clone());
575+
assert!(validate_assertion(&assertion).is_ok());
576+
577+
// Invalid - empty title
578+
let mut invalid = valid_ingredient.clone();
579+
invalid.title = "".to_string();
580+
let assertion = Assertion::Ingredient(invalid);
581+
assert!(validate_assertion(&assertion).is_err());
582+
583+
// Invalid - empty data types
584+
let mut invalid = valid_ingredient.clone();
585+
invalid.data.data_types = vec![];
586+
let assertion = Assertion::Ingredient(invalid);
587+
assert!(validate_assertion(&assertion).is_err());
588+
}
589+
590+
// Tests for CreativeWorkAssertion
591+
#[test]
592+
fn test_creative_work_assertion_validation() {
593+
// Valid CreativeWorkAssertion
594+
let valid_creative = CreativeWorkAssertion {
595+
context: "http://schema.org/".to_string(),
596+
creative_type: "Dataset".to_string(),
597+
author: vec![Author {
598+
author_type: "Person".to_string(),
599+
name: "Jane Doe".to_string(),
600+
}],
601+
};
602+
let assertion = Assertion::CreativeWork(valid_creative.clone());
603+
assert!(validate_assertion(&assertion).is_ok());
604+
605+
// Invalid - empty context
606+
let mut invalid = valid_creative.clone();
607+
invalid.context = "".to_string();
608+
let assertion = Assertion::CreativeWork(invalid);
609+
assert!(validate_assertion(&assertion).is_err());
610+
611+
// Invalid - empty authors
612+
let mut invalid = valid_creative.clone();
613+
invalid.author = vec![];
614+
let assertion = Assertion::CreativeWork(invalid);
615+
assert!(validate_assertion(&assertion).is_err());
616+
617+
// Invalid - author with empty name
618+
let invalid_author = CreativeWorkAssertion {
619+
context: "http://schema.org/".to_string(),
620+
creative_type: "Dataset".to_string(),
621+
author: vec![Author {
622+
author_type: "Person".to_string(),
623+
name: "".to_string(),
624+
}],
625+
};
626+
let assertion = Assertion::CreativeWork(invalid_author);
627+
assert!(validate_assertion(&assertion).is_err());
628+
}
629+
630+
// Tests for CustomAssertion
631+
#[test]
632+
fn test_custom_assertion_validation() {
633+
// Valid CustomAssertion
634+
let valid_custom = CustomAssertion {
635+
label: "c2pa.ml.custom".to_string(),
636+
data: json!({"key": "value"}),
637+
};
638+
let assertion = Assertion::CustomAssertion(valid_custom);
639+
assert!(validate_assertion(&assertion).is_ok());
640+
641+
// Invalid - empty label
642+
let invalid_label = CustomAssertion {
643+
label: "".to_string(),
644+
data: json!({"key": "value"}),
645+
};
646+
let assertion = Assertion::CustomAssertion(invalid_label);
647+
assert!(validate_assertion(&assertion).is_err());
648+
649+
// Invalid - null data
650+
let invalid_data = CustomAssertion {
651+
label: "c2pa.ml.custom".to_string(),
652+
data: serde_json::Value::Null,
653+
};
654+
let assertion = Assertion::CustomAssertion(invalid_data);
655+
assert!(validate_assertion(&assertion).is_err());
656+
}
657+
658+
// Tests for assertion label generation
659+
#[test]
660+
fn test_generate_assertion_label() {
661+
assert_eq!(generate_assertion_label("c2pa.action", None), "c2pa.action");
662+
assert_eq!(
663+
generate_assertion_label("c2pa.action", Some(0)),
664+
"c2pa.action__0"
665+
);
666+
assert_eq!(
667+
generate_assertion_label("c2pa.action", Some(42)),
668+
"c2pa.action__42"
669+
);
670+
}
671+
672+
// Tests for Assertion PartialEq implementation
673+
#[test]
674+
fn test_assertion_partial_eq() {
675+
// Hash assertions equality
676+
let hash1 = Assertion::Hash(HashAssertion {
677+
algorithm: "sha256".to_string(),
678+
hash_value: vec![1, 2, 3],
679+
});
680+
let hash2 = Assertion::Hash(HashAssertion {
681+
algorithm: "sha256".to_string(),
682+
hash_value: vec![1, 2, 3],
683+
});
684+
let hash3 = Assertion::Hash(HashAssertion {
685+
algorithm: "sha256".to_string(),
686+
hash_value: vec![4, 5, 6],
687+
});
688+
assert_eq!(hash1, hash2);
689+
assert_ne!(hash1, hash3);
690+
691+
// Ingredient assertions equality
692+
let ing1 = Assertion::Ingredient(IngredientAssertion {
693+
title: "Dataset".to_string(),
694+
relationship: "inputTo".to_string(),
695+
format: "application/zip".to_string(),
696+
document_id: "doc_123".to_string(),
697+
instance_id: "instance_123".to_string(),
698+
data: IngredientData {
699+
url: "https://example.com/data.zip".to_string(),
700+
alg: "sha256".to_string(),
701+
hash: "abc123".to_string(),
702+
data_types: vec!["dataset".to_string()],
703+
},
704+
});
705+
let ing2 = ing1.clone();
706+
let mut ing3 = ing1.clone();
707+
if let Assertion::Ingredient(ref mut ing) = ing3 {
708+
ing.document_id = "doc_456".to_string();
709+
}
710+
assert_eq!(ing1, ing2);
711+
assert_ne!(ing1, ing3);
712+
713+
// Different types are not equal
714+
assert_ne!(hash1, ing1);
715+
}
716+
717+
// Test serialization/deserialization of assertions
718+
#[test]
719+
fn test_assertion_serialization() {
720+
let original =
721+
Assertion::DoNotTrain(DoNotTrainAssertion::new("Test reason".to_string(), true));
722+
723+
// Serialize to JSON
724+
let serialized = serde_json::to_string(&original).unwrap();
725+
726+
// Deserialize back
727+
let deserialized: Assertion = serde_json::from_str(&serialized).unwrap();
728+
729+
// Verify they match
730+
if let (Assertion::DoNotTrain(orig), Assertion::DoNotTrain(deser)) =
731+
(&original, &deserialized)
732+
{
733+
assert_eq!(orig.reason, deser.reason);
734+
assert_eq!(orig.enforced, deser.enforced);
735+
} else {
736+
panic!("Deserialization produced wrong assertion type");
737+
}
738+
}
739+
}

0 commit comments

Comments
 (0)