@@ -449,6 +449,303 @@ public async Task Anonymization_ShouldLogErrorAndContinue_WhenOneItemFails()
449449 result [ 1 ] . STREET . Should ( ) . NotBe ( "Good Street" ) ;
450450 }
451451
452+ [ Fact ]
453+ public async Task GetSamCommonLandsAsync_Generic_ShouldAnonymizeAddressAndLocationFields ( )
454+ {
455+ // Arrange
456+ var commonLands = new List < SamCommonLand >
457+ {
458+ new ( )
459+ {
460+ COMMON_CPH = "00/000/0001" ,
461+ MAIN_CPH = "-" ,
462+ PREMISES_NAME = "Smiths Common Land" ,
463+ ADDRESS_LINE_1 = "123 Real Street" ,
464+ ADDRESS_LINE_2 = "Flat 5" ,
465+ ADDRESS_LINE_3 = "Manchester" ,
466+ POSTCODE = "M20 2XY" ,
467+ EASTING = "422473" ,
468+ NORTHING = "569204"
469+ }
470+ } ;
471+
472+ var response = new DataBridgeResponse < SamCommonLand >
473+ {
474+ CollectionName = "commonlands" ,
475+ Count = 1 ,
476+ TotalCount = 1 ,
477+ Data = commonLands
478+ } ;
479+
480+ _innerClient . GetSamCommonLandsAsync < SamCommonLand > ( 10 , 0 , null , null , null , Arg . Any < CancellationToken > ( ) )
481+ . Returns ( response ) ;
482+
483+ // Act
484+ var result = await _sut . GetSamCommonLandsAsync < SamCommonLand > ( 10 , 0 , cancellationToken : CancellationToken . None ) ;
485+
486+ // Assert
487+ result . Should ( ) . NotBeNull ( ) ;
488+ result ! . Data . Should ( ) . HaveCount ( 1 ) ;
489+
490+ var anonymized = result . Data [ 0 ] ;
491+
492+ // Non-PII fields should remain unchanged
493+ anonymized . COMMON_CPH . Should ( ) . Be ( "00/000/0001" ) ;
494+ anonymized . MAIN_CPH . Should ( ) . Be ( "-" ) ;
495+
496+ // PII fields should be anonymized
497+ anonymized . PREMISES_NAME . Should ( ) . NotBe ( "Smiths Common Land" ) . And . NotBeNullOrWhiteSpace ( ) ;
498+ anonymized . ADDRESS_LINE_1 . Should ( ) . NotBe ( "123 Real Street" ) . And . NotBeNullOrWhiteSpace ( ) ;
499+ anonymized . ADDRESS_LINE_2 . Should ( ) . NotBe ( "Flat 5" ) . And . NotBeNullOrWhiteSpace ( ) ;
500+ anonymized . ADDRESS_LINE_3 . Should ( ) . NotBe ( "Manchester" ) . And . NotBeNullOrWhiteSpace ( ) ;
501+ anonymized . POSTCODE . Should ( ) . NotBe ( "M20 2XY" ) . And . MatchRegex ( @"^[A-Z]{2}\d \d[A-Z]{2}$" ) ;
502+ anonymized . EASTING . Should ( ) . NotBe ( "422473" ) ;
503+ int . Parse ( anonymized . EASTING ! ) . Should ( ) . BeInRange ( 100000 , 999999 ) ;
504+ anonymized . NORTHING . Should ( ) . NotBe ( "569204" ) ;
505+ int . Parse ( anonymized . NORTHING ! ) . Should ( ) . BeInRange ( 200000 , 999999 ) ;
506+ }
507+
508+ [ Fact ]
509+ public async Task GetSamCommonLandsByCommonCphAsync_ShouldAnonymizeAllCommonLands ( )
510+ {
511+ // Arrange
512+ var commonLands = new List < SamCommonLand >
513+ {
514+ new ( )
515+ {
516+ COMMON_CPH = "00/000/0001" ,
517+ MAIN_CPH = "-" ,
518+ PREMISES_NAME = "Original Common Land" ,
519+ ADDRESS_LINE_1 = "Original Street" ,
520+ POSTCODE = "AB12 3CD" ,
521+ EASTING = "123456" ,
522+ NORTHING = "654321"
523+ } ,
524+ new ( )
525+ {
526+ COMMON_CPH = "00/000/0001" ,
527+ MAIN_CPH = "12/345/6789" ,
528+ CONTIGUOUS_COMMON = "Yes" ,
529+ START_DATE = "2020-01-01"
530+ }
531+ } ;
532+
533+ _innerClient . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , Arg . Any < CancellationToken > ( ) )
534+ . Returns ( commonLands ) ;
535+
536+ // Act
537+ var result = await _sut . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , CancellationToken . None ) ;
538+
539+ // Assert
540+ result . Should ( ) . HaveCount ( 2 ) ;
541+
542+ var definitionRecord = result [ 0 ] ;
543+ var relationshipRecord = result [ 1 ] ;
544+
545+ // Definition record - PII should be anonymized
546+ definitionRecord . COMMON_CPH . Should ( ) . Be ( "00/000/0001" ) ;
547+ definitionRecord . MAIN_CPH . Should ( ) . Be ( "-" ) ;
548+ definitionRecord . PREMISES_NAME . Should ( ) . NotBe ( "Original Common Land" ) . And . NotBeNullOrWhiteSpace ( ) ;
549+ definitionRecord . ADDRESS_LINE_1 . Should ( ) . NotBe ( "Original Street" ) . And . NotBeNullOrWhiteSpace ( ) ;
550+ definitionRecord . POSTCODE . Should ( ) . NotBe ( "AB12 3CD" ) . And . MatchRegex ( @"^[A-Z]{2}\d \d[A-Z]{2}$" ) ;
551+ definitionRecord . EASTING . Should ( ) . NotBe ( "123456" ) ;
552+ definitionRecord . NORTHING . Should ( ) . NotBe ( "654321" ) ;
553+
554+ // Relationship record - non-PII fields should remain unchanged
555+ relationshipRecord . COMMON_CPH . Should ( ) . Be ( "00/000/0001" ) ;
556+ relationshipRecord . MAIN_CPH . Should ( ) . Be ( "12/345/6789" ) ;
557+ relationshipRecord . CONTIGUOUS_COMMON . Should ( ) . Be ( "Yes" ) ;
558+ relationshipRecord . START_DATE . Should ( ) . Be ( "2020-01-01" ) ;
559+ }
560+
561+ [ Fact ]
562+ public async Task GetSamCommonLandsByCommonCphAsync_ShouldHandleNullFields ( )
563+ {
564+ // Arrange
565+ var commonLands = new List < SamCommonLand >
566+ {
567+ new ( )
568+ {
569+ COMMON_CPH = "00/000/0001" ,
570+ MAIN_CPH = "-" ,
571+ PREMISES_NAME = null ,
572+ ADDRESS_LINE_1 = null ,
573+ ADDRESS_LINE_2 = null ,
574+ ADDRESS_LINE_3 = null ,
575+ POSTCODE = null ,
576+ EASTING = null ,
577+ NORTHING = null
578+ }
579+ } ;
580+
581+ _innerClient . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , Arg . Any < CancellationToken > ( ) )
582+ . Returns ( commonLands ) ;
583+
584+ // Act
585+ var result = await _sut . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , CancellationToken . None ) ;
586+
587+ // Assert
588+ result . Should ( ) . HaveCount ( 1 ) ;
589+
590+ var anonymized = result [ 0 ] ;
591+ anonymized . PREMISES_NAME . Should ( ) . BeNull ( ) ;
592+ anonymized . ADDRESS_LINE_1 . Should ( ) . BeNull ( ) ;
593+ anonymized . ADDRESS_LINE_2 . Should ( ) . BeNull ( ) ;
594+ anonymized . ADDRESS_LINE_3 . Should ( ) . BeNull ( ) ;
595+ anonymized . POSTCODE . Should ( ) . BeNull ( ) ;
596+ anonymized . EASTING . Should ( ) . BeNull ( ) ;
597+ anonymized . NORTHING . Should ( ) . BeNull ( ) ;
598+ }
599+
600+ [ Fact ]
601+ public async Task GetSamCommonLandsByCommonCphAsync_ShouldNotAnonymizePlaceholderPremisesName ( )
602+ {
603+ // Arrange
604+ var commonLands = new List < SamCommonLand >
605+ {
606+ new ( )
607+ {
608+ COMMON_CPH = "00/000/0001" ,
609+ MAIN_CPH = "-" ,
610+ PREMISES_NAME = "-" ,
611+ ADDRESS_LINE_1 = "Real Address"
612+ }
613+ } ;
614+
615+ _innerClient . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , Arg . Any < CancellationToken > ( ) )
616+ . Returns ( commonLands ) ;
617+
618+ // Act
619+ var result = await _sut . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , CancellationToken . None ) ;
620+
621+ // Assert
622+ result . Should ( ) . HaveCount ( 1 ) ;
623+ result [ 0 ] . PREMISES_NAME . Should ( ) . Be ( "-" ) ;
624+ result [ 0 ] . ADDRESS_LINE_1 . Should ( ) . NotBe ( "Real Address" ) ;
625+ }
626+
627+ [ Fact ]
628+ public async Task GetSamCommonLandsAsync_ShouldProduceDeterministicResults ( )
629+ {
630+ // Arrange
631+ var commonLands1 = new List < SamCommonLand >
632+ {
633+ new ( )
634+ {
635+ COMMON_CPH = "00/000/0001" ,
636+ MAIN_CPH = "-" ,
637+ ADDRESS_LINE_1 = "Street 1" ,
638+ PREMISES_NAME = "Name 1"
639+ }
640+ } ;
641+
642+ var commonLands2 = new List < SamCommonLand >
643+ {
644+ new ( )
645+ {
646+ COMMON_CPH = "00/000/0001" ,
647+ MAIN_CPH = "-" ,
648+ ADDRESS_LINE_1 = "Different Street" ,
649+ PREMISES_NAME = "Different Name"
650+ }
651+ } ;
652+
653+ _innerClient . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , Arg . Any < CancellationToken > ( ) )
654+ . Returns ( commonLands1 , commonLands2 ) ;
655+
656+ // Act
657+ var result1 = await _sut . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , CancellationToken . None ) ;
658+ var result2 = await _sut . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , CancellationToken . None ) ;
659+
660+ // Assert - Same COMMON_CPH should produce same anonymized values
661+ result1 [ 0 ] . ADDRESS_LINE_1 . Should ( ) . Be ( result2 [ 0 ] . ADDRESS_LINE_1 ) ;
662+ result1 [ 0 ] . PREMISES_NAME . Should ( ) . Be ( result2 [ 0 ] . PREMISES_NAME ) ;
663+ }
664+
665+ [ Fact ]
666+ public async Task GetSamCommonLandsAsync_ShouldPreserveNonPiiFields ( )
667+ {
668+ // Arrange
669+ var commonLands = new List < SamCommonLand >
670+ {
671+ new ( )
672+ {
673+ BATCH_ID = 123 ,
674+ CHANGE_TYPE = "I" ,
675+ COMMON_CPH = "00/000/0001" ,
676+ MAIN_CPH = "12/345/6789" ,
677+ BUSINESS_USAGE = "Common Land" ,
678+ LOCAL_AUTH_NAME = "Test Council" ,
679+ COUNTRY = "England" ,
680+ CONTIGUOUS_COMMON = "Yes" ,
681+ START_DATE = "2020-01-01" ,
682+ END_DATE = "2024-12-31" ,
683+ ADDRESS_LINE_1 = "Secret Address" ,
684+ PREMISES_NAME = "Secret Name"
685+ }
686+ } ;
687+
688+ var response = new DataBridgeResponse < SamCommonLand >
689+ {
690+ CollectionName = "commonlands" ,
691+ Count = 1 ,
692+ Data = commonLands
693+ } ;
694+
695+ _innerClient . GetSamCommonLandsAsync < SamCommonLand > ( 10 , 0 , null , null , null , Arg . Any < CancellationToken > ( ) )
696+ . Returns ( response ) ;
697+
698+ // Act
699+ var result = await _sut . GetSamCommonLandsAsync < SamCommonLand > ( 10 , 0 , cancellationToken : CancellationToken . None ) ;
700+
701+ // Assert
702+ var anonymized = result ! . Data [ 0 ] ;
703+
704+ // Non-PII fields preserved
705+ anonymized . BATCH_ID . Should ( ) . Be ( 123 ) ;
706+ anonymized . CHANGE_TYPE . Should ( ) . Be ( "I" ) ;
707+ anonymized . COMMON_CPH . Should ( ) . Be ( "00/000/0001" ) ;
708+ anonymized . MAIN_CPH . Should ( ) . Be ( "12/345/6789" ) ;
709+ anonymized . BUSINESS_USAGE . Should ( ) . Be ( "Common Land" ) ;
710+ anonymized . LOCAL_AUTH_NAME . Should ( ) . Be ( "Test Council" ) ;
711+ anonymized . COUNTRY . Should ( ) . Be ( "England" ) ;
712+ anonymized . CONTIGUOUS_COMMON . Should ( ) . Be ( "Yes" ) ;
713+ anonymized . START_DATE . Should ( ) . Be ( "2020-01-01" ) ;
714+ anonymized . END_DATE . Should ( ) . Be ( "2024-12-31" ) ;
715+
716+ // PII fields anonymized
717+ anonymized . ADDRESS_LINE_1 . Should ( ) . NotBe ( "Secret Address" ) ;
718+ anonymized . PREMISES_NAME . Should ( ) . NotBe ( "Secret Name" ) ;
719+ }
720+
721+ [ Fact ]
722+ public async Task GetSamCommonLandsAsync_ShouldReturnNull_WhenInnerReturnsNull ( )
723+ {
724+ // Arrange
725+ _innerClient . GetSamCommonLandsAsync < SamCommonLand > ( 10 , 0 , null , null , null , Arg . Any < CancellationToken > ( ) )
726+ . Returns ( ( DataBridgeResponse < SamCommonLand > ? ) null ) ;
727+
728+ // Act
729+ var result = await _sut . GetSamCommonLandsAsync < SamCommonLand > ( 10 , 0 , cancellationToken : CancellationToken . None ) ;
730+
731+ // Assert
732+ result . Should ( ) . BeNull ( ) ;
733+ }
734+
735+ [ Fact ]
736+ public async Task GetSamCommonLandsByCommonCphAsync_ShouldHandleEmptyList ( )
737+ {
738+ // Arrange
739+ _innerClient . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , Arg . Any < CancellationToken > ( ) )
740+ . Returns ( [ ] ) ;
741+
742+ // Act
743+ var result = await _sut . GetSamCommonLandsByCommonCphAsync ( "00/000/0001" , CancellationToken . None ) ;
744+
745+ // Assert
746+ result . Should ( ) . NotBeNull ( ) . And . BeEmpty ( ) ;
747+ }
748+
452749 private static SamCphHolder CreateSamCphHolder ( ) => new ( )
453750 {
454751 PARTY_ID = "P001" ,
0 commit comments