@@ -721,6 +721,258 @@ private static byte[] CreateCompleteTag (Dictionary<string, string> items)
721721
722722 #endregion
723723
724+ #region ApeTagItem Coverage Tests
725+
726+ [ TestMethod ]
727+ public void ApeTagItem_CreateText_WithReadOnly_SetsFlag ( )
728+ {
729+ var item = ApeTagItem . CreateText ( "Title" , "Test" , isReadOnly : true ) ;
730+ Assert . IsTrue ( item . IsReadOnly ) ;
731+ Assert . AreEqual ( ApeItemType . Text , item . ItemType ) ;
732+ }
733+
734+ [ TestMethod ]
735+ public void ApeTagItem_CreateBinary_WithReadOnly_SetsFlag ( )
736+ {
737+ var item = ApeTagItem . CreateBinary ( "Cover" , "cover.jpg" , [ 0xFF , 0xD8 ] , isReadOnly : true ) ;
738+ Assert . IsTrue ( item . IsReadOnly ) ;
739+ Assert . AreEqual ( ApeItemType . Binary , item . ItemType ) ;
740+ }
741+
742+ [ TestMethod ]
743+ public void ApeTagItem_CreateExternalLocator_WithReadOnly_SetsFlag ( )
744+ {
745+ var item = ApeTagItem . CreateExternalLocator ( "Link" , "http://example.com" , isReadOnly : true ) ;
746+ Assert . IsTrue ( item . IsReadOnly ) ;
747+ Assert . AreEqual ( ApeItemType . ExternalLocator , item . ItemType ) ;
748+ }
749+
750+ [ TestMethod ]
751+ public void ApeTagItem_Render_RoundTrips ( )
752+ {
753+ var original = ApeTagItem . CreateText ( "Title" , "Test Value" ) ;
754+ var rendered = original . Render ( ) ;
755+ var reparsed = ApeTagItem . Parse ( rendered ) ;
756+
757+ Assert . IsTrue ( reparsed . IsSuccess ) ;
758+ Assert . AreEqual ( "Title" , reparsed . Item ! . Key ) ;
759+ Assert . AreEqual ( "Test Value" , reparsed . Item . ValueAsString ) ;
760+ }
761+
762+ [ TestMethod ]
763+ public void ApeTagItem_BinaryValue_NoFilename_ReturnsDataOnly ( )
764+ {
765+ // Create binary item where the value has no null separator (no filename)
766+ var keyBytes = "Cover"u8 . ToArray ( ) ;
767+ var binaryData = new byte [ ] { 0xFF , 0xD8 , 0xFF , 0xE0 } ;
768+ var itemData = new byte [ 8 + keyBytes . Length + 1 + binaryData . Length ] ;
769+
770+ BitConverter . GetBytes ( ( uint ) binaryData . Length ) . CopyTo ( itemData , 0 ) ;
771+ BitConverter . GetBytes ( 2u ) . CopyTo ( itemData , 4 ) ; // Flags = 2 (binary type)
772+ keyBytes . CopyTo ( itemData , 8 ) ;
773+ binaryData . CopyTo ( itemData , 8 + keyBytes . Length + 1 ) ;
774+
775+ var result = ApeTagItem . Parse ( itemData ) ;
776+
777+ Assert . IsTrue ( result . IsSuccess ) ;
778+ Assert . AreEqual ( ApeItemType . Binary , result . Item ! . ItemType ) ;
779+ var bv = result . Item . BinaryValue ;
780+ Assert . IsNotNull ( bv ) ;
781+ Assert . AreEqual ( "" , bv . Filename ) ;
782+ CollectionAssert . AreEqual ( binaryData , bv . Data ) ;
783+ }
784+
785+ [ TestMethod ]
786+ public void ApeTagItem_ValueAsString_ForBinary_ReturnsNull ( )
787+ {
788+ var item = ApeTagItem . CreateBinary ( "Cover" , "cover.jpg" , [ 0x89 , 0x50 ] ) ;
789+ Assert . IsNull ( item . ValueAsString ) ;
790+ }
791+
792+ [ TestMethod ]
793+ public void ApeTagItem_BinaryValue_ForText_ReturnsNull ( )
794+ {
795+ var item = ApeTagItem . CreateText ( "Title" , "Test" ) ;
796+ Assert . IsNull ( item . BinaryValue ) ;
797+ }
798+
799+ [ TestMethod ]
800+ public void ApeTagItem_Parse_SingleCharKey_Fails ( )
801+ {
802+ var data = CreateTextItem ( "X" , "value" ) ;
803+ var result = ApeTagItem . Parse ( data ) ;
804+ Assert . IsFalse ( result . IsSuccess ) ;
805+ Assert . IsTrue ( result . Error ! . Contains ( "short" ) ) ;
806+ }
807+
808+ [ TestMethod ]
809+ public void ApeTagItem_Parse_TwoCharKey_Succeeds ( )
810+ {
811+ var data = CreateTextItem ( "XY" , "value" ) ;
812+ var result = ApeTagItem . Parse ( data ) ;
813+ Assert . IsTrue ( result . IsSuccess ) ;
814+ Assert . AreEqual ( "XY" , result . Item ! . Key ) ;
815+ }
816+
817+ [ TestMethod ]
818+ public void ApeTagItem_Parse_InvalidFlagsAreMasked ( )
819+ {
820+ var keyBytes = "Title"u8 . ToArray ( ) ;
821+ var valueBytes = "Test"u8 . ToArray ( ) ;
822+ var data = new byte [ 8 + keyBytes . Length + 1 + valueBytes . Length ] ;
823+
824+ BitConverter . GetBytes ( ( uint ) valueBytes . Length ) . CopyTo ( data , 0 ) ;
825+ BitConverter . GetBytes ( 0xF8u ) . CopyTo ( data , 4 ) ; // Invalid flag bits
826+ keyBytes . CopyTo ( data , 8 ) ;
827+ valueBytes . CopyTo ( data , 8 + keyBytes . Length + 1 ) ;
828+
829+ var result = ApeTagItem . Parse ( data ) ;
830+
831+ Assert . IsTrue ( result . IsSuccess ) ;
832+ Assert . AreEqual ( ApeItemType . Text , result . Item ! . ItemType ) ;
833+ }
834+
835+ [ TestMethod ]
836+ public void ApeTagItem_Parse_ValueExtendsBeyondData_Fails ( )
837+ {
838+ var keyBytes = "Title"u8 . ToArray ( ) ;
839+ var data = new byte [ 8 + keyBytes . Length + 1 + 5 ] ;
840+
841+ BitConverter . GetBytes ( 100u ) . CopyTo ( data , 0 ) ; // Claim 100 bytes
842+ keyBytes . CopyTo ( data , 8 ) ;
843+
844+ var result = ApeTagItem . Parse ( data ) ;
845+
846+ Assert . IsFalse ( result . IsSuccess ) ;
847+ Assert . IsTrue ( result . Error ! . Contains ( "extends" ) || result . Error . Contains ( "beyond" ) ) ;
848+ }
849+
850+ [ TestMethod ]
851+ public void ApeTagItem_CreateText_ShortKey_Throws ( )
852+ {
853+ Assert . ThrowsExactly < ArgumentException > ( ( ) =>
854+ ApeTagItem . CreateText ( "X" , "value" ) ) ;
855+ }
856+
857+ [ TestMethod ]
858+ public void ApeTagItem_CreateText_LongKey_Throws ( )
859+ {
860+ var longKey = new string ( 'K' , 256 ) ;
861+ Assert . ThrowsExactly < ArgumentException > ( ( ) =>
862+ ApeTagItem . CreateText ( longKey , "value" ) ) ;
863+ }
864+
865+ [ TestMethod ]
866+ public void ApeTagItem_CreateText_ReservedKey_Throws ( )
867+ {
868+ Assert . ThrowsExactly < ArgumentException > ( ( ) =>
869+ ApeTagItem . CreateText ( "ID3" , "value" ) ) ;
870+ Assert . ThrowsExactly < ArgumentException > ( ( ) =>
871+ ApeTagItem . CreateText ( "TAG" , "value" ) ) ;
872+ Assert . ThrowsExactly < ArgumentException > ( ( ) =>
873+ ApeTagItem . CreateText ( "OggS" , "value" ) ) ;
874+ Assert . ThrowsExactly < ArgumentException > ( ( ) =>
875+ ApeTagItem . CreateText ( "MP+" , "value" ) ) ;
876+ }
877+
878+ [ TestMethod ]
879+ public void ApeTagItem_CreateText_ControlCharInKey_Throws ( )
880+ {
881+ Assert . ThrowsExactly < ArgumentException > ( ( ) =>
882+ ApeTagItem . CreateText ( "Title\x01 " , "value" ) ) ;
883+ }
884+
885+ [ TestMethod ]
886+ public void ApeTagItemParseResult_OperatorEquals_Works ( )
887+ {
888+ var failure1 = ApeTagItemParseResult . Failure ( "Error A" ) ;
889+ var failure2 = ApeTagItemParseResult . Failure ( "Error A" ) ;
890+ var failure3 = ApeTagItemParseResult . Failure ( "Error B" ) ;
891+
892+ Assert . IsTrue ( failure1 == failure2 ) ;
893+ Assert . IsFalse ( failure1 == failure3 ) ;
894+ }
895+
896+ [ TestMethod ]
897+ public void ApeTagItemParseResult_OperatorNotEquals_Works ( )
898+ {
899+ var failure1 = ApeTagItemParseResult . Failure ( "Error A" ) ;
900+ var failure2 = ApeTagItemParseResult . Failure ( "Error B" ) ;
901+
902+ Assert . IsTrue ( failure1 != failure2 ) ;
903+ }
904+
905+ [ TestMethod ]
906+ public void ApeTagItem_Parse_NoKeyTerminator_Fails ( )
907+ {
908+ var data = new byte [ 20 ] ;
909+ BitConverter . GetBytes ( 4u ) . CopyTo ( data , 0 ) ;
910+ for ( int i = 8 ; i < data . Length ; i ++ )
911+ data [ i ] = ( byte ) 'A' ;
912+
913+ var result = ApeTagItem . Parse ( data ) ;
914+ Assert . IsFalse ( result . IsSuccess ) ;
915+ Assert . IsTrue ( result . Error ! . Contains ( "null" ) ) ;
916+ }
917+
918+ #endregion
919+
920+ #region ApeTagHeader Coverage Tests
921+
922+ [ TestMethod ]
923+ public void ApeTagHeader_Create_ReadOnly_SetsFlag ( )
924+ {
925+ var header = ApeTagHeader . Create ( 100 , 5 , isReadOnly : true ) ;
926+ Assert . IsTrue ( header . IsReadOnly ) ;
927+ Assert . IsTrue ( header . IsHeader ) ;
928+ Assert . IsTrue ( header . HasHeader ) ;
929+ }
930+
931+ [ TestMethod ]
932+ public void ApeTagHeader_Render_RoundTrips ( )
933+ {
934+ var original = ApeTagHeader . Create ( 200 , 10 ) ;
935+ var rendered = original . Render ( ) ;
936+ var reparsed = ApeTagHeader . Parse ( rendered ) ;
937+
938+ Assert . IsTrue ( reparsed . IsSuccess ) ;
939+ Assert . AreEqual ( original . TagSize , reparsed . Header ! . TagSize ) ;
940+ Assert . AreEqual ( original . ItemCount , reparsed . Header . ItemCount ) ;
941+ Assert . IsTrue ( reparsed . Header . IsHeader ) ;
942+ }
943+
944+ [ TestMethod ]
945+ public void ApeTagHeader_Parse_FooterData_Fails ( )
946+ {
947+ var footer = ApeTagFooter . Create ( 100 , 5 , isHeader : false ) ;
948+ var data = footer . Render ( ) ;
949+
950+ var result = ApeTagHeader . Parse ( data ) ;
951+
952+ Assert . IsFalse ( result . IsSuccess ) ;
953+ Assert . IsTrue ( result . Error ! . Contains ( "header" ) || result . Error . Contains ( "bit 29" ) ) ;
954+ }
955+
956+ [ TestMethod ]
957+ public void ApeTagHeaderParseResult_OperatorEquals_Works ( )
958+ {
959+ var failure1 = ApeTagHeaderParseResult . Failure ( "Error A" ) ;
960+ var failure2 = ApeTagHeaderParseResult . Failure ( "Error A" ) ;
961+
962+ Assert . IsTrue ( failure1 == failure2 ) ;
963+ }
964+
965+ [ TestMethod ]
966+ public void ApeTagHeaderParseResult_OperatorNotEquals_Works ( )
967+ {
968+ var failure1 = ApeTagHeaderParseResult . Failure ( "Error A" ) ;
969+ var failure2 = ApeTagHeaderParseResult . Failure ( "Error B" ) ;
970+
971+ Assert . IsTrue ( failure1 != failure2 ) ;
972+ }
973+
974+ #endregion
975+
724976 #region Result Type Equality Tests
725977
726978 [ TestMethod ]
0 commit comments