@@ -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