@@ -94,6 +94,7 @@ private static EncryptionOptions CreateOptions(IEnumerable<string> paths, Compre
9494 [ TestMethod ]
9595 public async Task Decrypt_AllPrimitiveTypesAndContainers ( )
9696 {
97+ // Arrange
9798 var doc = new
9899 {
99100 id = Guid . NewGuid ( ) . ToString ( ) ,
@@ -137,6 +138,7 @@ public async Task Decrypt_AllPrimitiveTypesAndContainers()
137138 [ TestMethod ]
138139 public async Task Decrypt_CompressedPayloads ( )
139140 {
141+ // Arrange
140142 var doc = new
141143 {
142144 id = Guid . NewGuid ( ) . ToString ( ) ,
@@ -151,12 +153,14 @@ public async Task Decrypt_CompressedPayloads()
151153 Assert . IsNotNull ( props . CompressedEncryptedPaths ) ;
152154 Assert . IsTrue ( props . CompressedEncryptedPaths . Count > 0 ) ; // at least LargeStr is compressed
153155
156+ // Act
154157 MemoryStream output = new ( ) ;
155158 DecryptionContext ctx = await new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
156159 output . Position = 0 ;
157160 using JsonDocument jd = JsonDocument . Parse ( output ) ;
158161 JsonElement root = jd . RootElement ;
159162
163+ // Assert
160164 foreach ( string p in paths )
161165 {
162166 Assert . IsTrue ( ctx . DecryptionInfoList [ 0 ] . PathsDecrypted . Contains ( p ) ) ;
@@ -192,15 +196,18 @@ public async Task Decrypt_Throws_OnUnknownCompressionAlgorithm()
192196 [ TestMethod ]
193197 public async Task Decrypt_Skips_EncryptionInfo_Block ( )
194198 {
199+ // Arrange
195200 // Build a document that already has _ei. (Encryptor will append another one during encryption; we want to ensure decryptor skips only the encrypted one at top-level.)
196201 var doc = new { id = "1" , _ei = new { ignore = true } , SensitiveStr = "abc" } ;
197202 string [ ] paths = new [ ] { "/SensitiveStr" } ;
198203 EncryptionOptions options = CreateOptions ( paths ) ;
199204 ( MemoryStream encrypted , EncryptionProperties props ) = await EncryptRawAsync ( doc , options ) ;
200205
206+ // Act
201207 MemoryStream output = new ( ) ;
202208 DecryptionContext ctx = await new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
203209
210+ // Assert
204211 output . Position = 0 ;
205212 using JsonDocument jd = JsonDocument . Parse ( output ) ;
206213 JsonElement root = jd . RootElement ;
@@ -212,34 +219,40 @@ public async Task Decrypt_Skips_EncryptionInfo_Block()
212219 [ TestMethod ]
213220 public async Task Decrypt_IgnoresUnknownPropertyTypesAndMaintainsJson ( )
214221 {
222+ // Arrange
215223 var doc = new { id = "1" , SensitiveStr = "abc" , Regular = 5 } ;
216224 string [ ] paths = new [ ] { "/SensitiveStr" } ;
217225 EncryptionOptions options = CreateOptions ( paths ) ;
218226 ( MemoryStream encrypted , EncryptionProperties props ) = await EncryptRawAsync ( doc , options ) ;
219227
228+ // Act
220229 MemoryStream output = new ( ) ;
221230 _ = await new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
222231 output . Position = 0 ;
223232 using JsonDocument jd = JsonDocument . Parse ( output ) ;
233+ // Assert
224234 JsonElement root = jd . RootElement ;
225235 Assert . AreEqual ( 5 , root . GetProperty ( "Regular" ) . GetInt32 ( ) ) ;
226236 }
227237
228238 [ TestMethod ]
229239 public async Task Decrypt_Throws_OnUnknownEncryptionFormatVersion ( )
230240 {
241+ // Arrange
231242 var doc = new { id = "1" , SensitiveStr = "abc" } ;
232243 string [ ] paths = new [ ] { "/SensitiveStr" } ;
233244 EncryptionOptions options = CreateOptions ( paths ) ;
234245 ( MemoryStream encrypted , EncryptionProperties props ) = await EncryptRawAsync ( doc , options ) ;
235246 EncryptionProperties invalid = new EncryptionProperties ( 999 , props . EncryptionAlgorithm , props . DataEncryptionKeyId , null , props . EncryptedPaths , props . CompressionAlgorithm , props . CompressedEncryptedPaths ) ;
247+ // Act + Assert
236248 MemoryStream output = new ( ) ;
237249 await Assert . ThrowsExceptionAsync < NotSupportedException > ( ( ) => new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , invalid , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ) ;
238250 }
239251
240252 [ TestMethod ]
241253 public async Task Decrypt_Throws_OnInvalidBase64Ciphertext ( )
242254 {
255+ // Arrange
243256 var doc = new { id = "1" , SensitiveStr = "abc" } ;
244257 string [ ] paths = new [ ] { "/SensitiveStr" } ;
245258 EncryptionOptions options = CreateOptions ( paths ) ;
@@ -251,6 +264,7 @@ public async Task Decrypt_Throws_OnInvalidBase64Ciphertext()
251264 string corruptedCipher = string . Concat ( "#" , originalCipher . AsSpan ( 1 ) ) ; // invalid base64 start
252265 jsonText = jsonText . Replace ( "\" SensitiveStr\" :\" " + originalCipher + "\" " , "\" SensitiveStr\" :\" " + corruptedCipher + "\" " ) ;
253266 MemoryStream corruptedStream = new ( Encoding . UTF8 . GetBytes ( jsonText ) ) ;
267+ // Act + Assert
254268 MemoryStream output = new ( ) ;
255269 await Assert . ThrowsExceptionAsync < InvalidOperationException > ( ( ) => new StreamProcessor ( ) . DecryptStreamAsync ( corruptedStream , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ) ;
256270 }
@@ -318,6 +332,7 @@ public async Task Decrypt_Throws_OnCompressedPayload_DestinationTooSmall()
318332 [ TestMethod ]
319333 public async Task Decrypt_IgnoredBlock_PartialEiSkip ( )
320334 {
335+ // Arrange
321336 // Force the _ei metadata object to span multiple buffer reads so Utf8JsonReader.TrySkip() returns false,
322337 // exercising the fallback isIgnoredBlock path.
323338 const int propertyCount = 250 ; // large to inflate _ei encrypted paths list
@@ -340,11 +355,13 @@ public async Task Decrypt_IgnoredBlock_PartialEiSkip()
340355 StreamProcessor . InitialBufferSize = 32 ;
341356 try
342357 {
358+ // Act
343359 MemoryStream output = new ( ) ;
344360 DecryptionContext ctx = await new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
345361 output . Position = 0 ;
346362 using JsonDocument jd = JsonDocument . Parse ( output , new JsonDocumentOptions { AllowTrailingCommas = true } ) ;
347363 JsonElement root = jd . RootElement ;
364+ // Assert
348365 // _ei must be removed
349366 Assert . IsFalse ( root . TryGetProperty ( Constants . EncryptedInfo , out _ ) , "_ei should be skipped" ) ;
350367 // spot check a few decrypted properties
@@ -362,6 +379,7 @@ public async Task Decrypt_IgnoredBlock_PartialEiSkip()
362379 [ TestMethod ]
363380 public async Task Decrypt_CompressedPathsPropertyNull ( )
364381 {
382+ // Arrange
365383 // Cover branch where EncryptionProperties.CompressedEncryptedPaths is null (as opposed to empty dictionary or populated),
366384 // exercising the null path of the null-conditional operator in: bool containsCompressed = properties.CompressedEncryptedPaths?.Count > 0;
367385 var doc = new { id = "1" , SensitiveStr = "abc" } ;
@@ -380,9 +398,11 @@ public async Task Decrypt_CompressedPathsPropertyNull()
380398 compressedEncryptedPaths : null ) ;
381399
382400 encrypted . Position = 0 ;
401+ // Act
383402 MemoryStream output = new ( ) ;
384403 DecryptionContext ctx = await new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , propsNullCompressed , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
385404
405+ // Assert
386406 output . Position = 0 ;
387407 using JsonDocument jd = JsonDocument . Parse ( output ) ;
388408 Assert . AreEqual ( "abc" , jd . RootElement . GetProperty ( "SensitiveStr" ) . GetString ( ) ) ;
@@ -392,6 +412,7 @@ public async Task Decrypt_CompressedPathsPropertyNull()
392412 [ TestMethod ]
393413 public async Task Decrypt_UnencryptedArrayAndBooleans ( )
394414 {
415+ // Arrange
395416 // Covers StartArray / EndArray / True / False switch branches where decryptPropertyName == null (no encryption for those tokens).
396417 var doc = new
397418 {
@@ -405,9 +426,11 @@ public async Task Decrypt_UnencryptedArrayAndBooleans()
405426 EncryptionOptions options = CreateOptions ( paths ) ;
406427 ( MemoryStream encrypted , EncryptionProperties props ) = await EncryptRawAsync ( doc , options ) ;
407428
429+ // Act
408430 MemoryStream output = new ( ) ;
409431 DecryptionContext ctx = await new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
410432
433+ // Assert
411434 output . Position = 0 ;
412435 using JsonDocument jd = JsonDocument . Parse ( output ) ;
413436 JsonElement root = jd . RootElement ;
@@ -429,6 +452,7 @@ public async Task Decrypt_UnencryptedArrayAndBooleans()
429452 [ TestMethod ]
430453 public async Task Decrypt_ForgedCipherText_TypeMarkerNull ( )
431454 {
455+ // Arrange
432456 // Covers TypeMarker.Null switch branch by forging a ciphertext with first byte = Null marker.
433457 var doc = new { id = "1" , SensitiveStr = "abc" } ;
434458 string [ ] paths = new [ ] { "/SensitiveStr" } ;
@@ -457,10 +481,12 @@ public async Task Decrypt_ForgedCipherText_TypeMarkerNull()
457481 }
458482 forged . Position = 0 ;
459483
484+ // Act
460485 // Use custom encryptor that returns empty plaintext for Null marker
461486 StreamProcessor sp = new StreamProcessor { Encryptor = new NullMarkerMdeEncryptor ( ) } ;
462487 MemoryStream output = new ( ) ;
463488 DecryptionContext ctx = await sp . DecryptStreamAsync ( forged , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
489+ // Assert
464490 output . Position = 0 ;
465491 using JsonDocument outDoc = JsonDocument . Parse ( output ) ;
466492 Assert . AreEqual ( JsonValueKind . Null , outDoc . RootElement . GetProperty ( "SensitiveStr" ) . ValueKind ) ;
@@ -470,6 +496,7 @@ public async Task Decrypt_ForgedCipherText_TypeMarkerNull()
470496 [ TestMethod ]
471497 public async Task Decrypt_Throws_OnMissingDataEncryptionKey ( )
472498 {
499+ // Arrange
473500 // Arrange: create a valid encrypted payload
474501 var doc = new { id = "1" , SensitiveStr = "abc" } ;
475502 string [ ] paths = new [ ] { "/SensitiveStr" } ;
@@ -579,6 +606,7 @@ public async Task Decrypt_ForgedUnknownTypeMarker_WritesRaw_InvalidJson()
579606 }
580607 forged . Position = 0 ;
581608
609+ // Act
582610 // Use a bypass encryptor to return raw bytes that are not valid JSON, exercising the default branch (WriteRawValue)
583611 StreamProcessor sp = new StreamProcessor { Encryptor = new AlwaysPlaintextMdeEncryptor ( "NOT_JSON" ) } ;
584612 MemoryStream output = new ( ) ;
@@ -624,6 +652,7 @@ public async Task Decrypt_ForgedTypeMarkerLong_InvalidPayload_Throws()
624652 }
625653 forged . Position = 0 ;
626654
655+ // Act
627656 // Use encryptor that returns a plaintext that is invalid for a long serializer
628657 StreamProcessor sp = new StreamProcessor { Encryptor = new AlwaysPlaintextMdeEncryptor ( "abc" ) } ;
629658 MemoryStream output = new ( ) ;
@@ -642,6 +671,7 @@ public async Task Decrypt_ForgedTypeMarkerLong_InvalidPayload_Throws()
642671 [ TestMethod ]
643672 public async Task Decrypt_CompressedContainsPathButNotCurrentProperty ( )
644673 {
674+ // Arrange
645675 // Scenario: compression enabled, some paths compressed, but the current decryptPropertyName is NOT in CompressedEncryptedPaths.
646676 // Covers branch: containsCompressed == true && TryGetValue == false so decompression block is skipped.
647677 var doc = new
@@ -656,11 +686,13 @@ public async Task Decrypt_CompressedContainsPathButNotCurrentProperty()
656686 Assert . IsTrue ( props . CompressedEncryptedPaths . ContainsKey ( "/LargeStr" ) ) ;
657687 Assert . IsFalse ( props . CompressedEncryptedPaths . ContainsKey ( "/OtherStr" ) ) ;
658688
689+ // Act
659690 MemoryStream output = new ( ) ;
660691 DecryptionContext ctx = await new StreamProcessor ( ) . DecryptStreamAsync ( encrypted , output , mockEncryptor . Object , props , new CosmosDiagnosticsContext ( ) , CancellationToken . None ) ;
661692 output . Position = 0 ;
662693 using JsonDocument jd = JsonDocument . Parse ( output ) ;
663694 JsonElement root = jd . RootElement ;
695+ // Assert
664696 Assert . AreEqual ( 400 , root . GetProperty ( "LargeStr" ) . GetString ( ) . Length ) ;
665697 Assert . AreEqual ( 50 , root . GetProperty ( "OtherStr" ) . GetString ( ) . Length ) ;
666698 // Both should appear in decrypted paths
@@ -671,6 +703,7 @@ public async Task Decrypt_CompressedContainsPathButNotCurrentProperty()
671703 [ TestMethod ]
672704 public async Task Decrypt_Fuzz_Ciphertext_Length_And_TypeMarker_CrossProduct ( )
673705 {
706+ // Arrange
674707 // Property-style fuzzing across type markers and plaintext lengths. We don't assert per-iteration outcomes;
675708 // instead we ensure a wide set runs without catastrophic failures and that some known-good cases succeed.
676709 var doc = new { id = "1" , V = "seed" } ;
@@ -698,6 +731,7 @@ public async Task Decrypt_Fuzz_Ciphertext_Length_And_TypeMarker_CrossProduct()
698731 StreamProcessor sp = new StreamProcessor { Encryptor = new MutablePlaintextMdeEncryptor ( ) } ;
699732 MutablePlaintextMdeEncryptor mut = ( MutablePlaintextMdeEncryptor ) sp . Encryptor ;
700733
734+ // Act
701735 for ( int len = 0 ; len <= maxLen ; len ++ )
702736 {
703737 for ( int m = 0 ; m < markers . Length ; m ++ )
@@ -784,6 +818,7 @@ public async Task Decrypt_Fuzz_Ciphertext_Length_And_TypeMarker_CrossProduct()
784818 }
785819 }
786820
821+ // Assert
787822 Assert . IsTrue ( attempts > 0 , "No fuzz attempts executed" ) ;
788823 Assert . IsTrue ( successes > 0 , "Expected at least some successful decrypt/writes during fuzzing" ) ;
789824 }
0 commit comments