11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33
4- using System ;
54using System . Collections . Generic ;
65using System . Linq ;
7- using System . Reflection ;
8- using System . Threading ;
9- using System . Threading . Tasks ;
10- using Azure ;
11- using Azure . Storage . Blobs ;
12- using Azure . Storage . Blobs . Models ;
13- using Microsoft . Extensions . Logging ;
14- using Microsoft . Extensions . Logging . Abstractions ;
15- using Moq ;
166using NUnit . Framework ;
177
188namespace Microsoft . Azure . WebJobs . Extensions . Storage . Blobs . Listeners
199{
2010 public class BlobLogListenerTests
2111 {
22- [ TestCase ( "write" , 1 , null , 0 , true , TestName = "HasBlobWritesAsync_WriteLogPresent_ReturnsTrue" ) ]
23- [ TestCase ( "read" , 1 , null , 0 , false , TestName = "HasBlobWritesAsync_NonWriteLogPresent_ReturnsFalse" ) ]
24- [ TestCase ( null , 1 , null , 0 , false , TestName = "HasBlobWritesAsync_NoBlobs_ReturnsFalse" ) ]
25- [ TestCase ( "read" , 100 , "write" , 1 , true , TestName = "HasBlobWritesAsync_WriteLogPresentMultipleLogBlobs_ReturnsTrue" ) ]
26- [ TestCase ( "delete" , 100 , "read" , 100 , false , TestName = "HasBlobWritesAsync_NonWriteLogPresentMultipleLogBlobs_ReturnsFalse" ) ]
27- public async Task HasBlobWritesAsync_VariousCases ( string logType1 , int logType1Count , string logType2 , int logType2Count , bool expected )
28- {
29- // Arrange
30- var blobServiceClientMock = new Mock < BlobServiceClient > ( MockBehavior . Strict ) ;
31- var containerClientMock = new Mock < BlobContainerClient > ( MockBehavior . Strict ) ;
32-
33- TestAsyncPageable < BlobItem > pageable ;
34- var blobItems = new List < BlobItem > ( ) ;
35- if ( logType1 != null )
36- {
37- for ( int i = 0 ; i < logType1Count ; i ++ )
38- {
39- var blobItem = BlobItemFactory . Create (
40- name : Guid . NewGuid ( ) . ToString ( ) ,
41- metadata : new Dictionary < string , string > { { "LogType" , logType1 } } ) ;
42- blobItems . Add ( blobItem ) ;
43- }
44- }
45- if ( logType2 != null )
46- {
47- for ( int i = 0 ; i < logType2Count ; i ++ )
48- {
49- var blobItem = BlobItemFactory . Create (
50- name : Guid . NewGuid ( ) . ToString ( ) ,
51- metadata : new Dictionary < string , string > { { "LogType" , logType2 } } ) ;
52- blobItems . Add ( blobItem ) ;
53- }
54- }
55-
56- if ( blobItems . Count == 0 )
57- {
58- pageable = new TestAsyncPageable < BlobItem > ( Enumerable . Empty < BlobItem > ( ) ) ;
59- }
60- else
61- {
62- pageable = new TestAsyncPageable < BlobItem > ( blobItems ) ;
63- }
64-
65- containerClientMock
66- . Setup ( c => c . GetBlobsAsync (
67- It . IsAny < BlobTraits > ( ) ,
68- It . IsAny < BlobStates > ( ) ,
69- It . IsAny < string > ( ) ,
70- It . IsAny < CancellationToken > ( ) ) )
71- . Returns ( pageable ) ;
72-
73- blobServiceClientMock
74- . Setup ( c => c . GetBlobContainerClient ( "$logs" ) )
75- . Returns ( containerClientMock . Object ) ;
76-
77- var listener = new BlobLogListener ( blobServiceClientMock . Object , NullLogger < BlobListener > . Instance ) ;
78-
79- // Act
80- bool result = await listener . HasBlobWritesAsync ( CancellationToken . None , hoursWindow : 1 ) ;
81-
82- // Assert
83- Assert . AreEqual ( expected , result ) ;
84- }
85-
8612 [ Test ]
8713 public void GetPathsForValidBlobWrites_Returns_ValidBlobWritesOnly ( )
8814 {
8915 StorageAnalyticsLogEntry [ ] entries = new [ ]
9016 {
17+ // This is a valid write entry with a valid path
9118 new StorageAnalyticsLogEntry
9219 {
9320 ServiceType = StorageServiceType . Blob ,
9421 OperationType = StorageServiceOperationType . PutBlob ,
9522 RequestedObjectKey = @"/storagesample/sample-container/""0x8D199A96CB71468""/sample-blob.txt"
9623 } ,
24+
25+ // This is an invalid path and will be filtered out
9726 new StorageAnalyticsLogEntry
9827 {
9928 ServiceType = StorageServiceType . Blob ,
10029 OperationType = StorageServiceOperationType . PutBlob ,
10130 RequestedObjectKey = "/"
10231 } ,
32+
33+ // This does not constitute a write and will be filtered out
10334 new StorageAnalyticsLogEntry
10435 {
10536 ServiceType = StorageServiceType . Blob ,
@@ -114,52 +45,5 @@ public void GetPathsForValidBlobWrites_Returns_ValidBlobWritesOnly()
11445 Assert . AreEqual ( "sample-container" , singlePath . ContainerName ) ;
11546 Assert . AreEqual ( @"""0x8D199A96CB71468""/sample-blob.txt" , singlePath . BlobName ) ;
11647 }
117-
118- private static class BlobItemFactory
119- {
120- private static readonly Type BlobItemType = typeof ( BlobItem ) ;
121- private static readonly System . Reflection . PropertyInfo NameProp = BlobItemType . GetProperty ( nameof ( BlobItem . Name ) ) ! ;
122- private static readonly System . Reflection . PropertyInfo MetadataProp = BlobItemType . GetProperty ( nameof ( BlobItem . Metadata ) ) ! ;
123-
124- public static BlobItem Create ( string name , IDictionary < string , string > metadata )
125- {
126- var ctor = BlobItemType . GetConstructor ( BindingFlags . Instance | BindingFlags . NonPublic , binder : null , types : Type . EmptyTypes , modifiers : null )
127- ?? throw new InvalidOperationException ( "BlobItem internal constructor not found." ) ;
128- object raw = ctor . Invoke ( null ) ;
129-
130- NameProp . SetValue ( raw , name ) ;
131- var dict = new Dictionary < string , string > ( metadata , StringComparer . OrdinalIgnoreCase ) ;
132- MetadataProp . SetValue ( raw , dict ) ;
133-
134- return ( BlobItem ) raw ;
135- }
136- }
137-
138- private sealed class TestAsyncPageable < T > : AsyncPageable < T >
139- {
140- private readonly IReadOnlyList < Page < T > > _pages ;
141-
142- public TestAsyncPageable ( IEnumerable < T > items )
143- {
144- var list = items . ToList ( ) ;
145- var responseMock = new Mock < Response > ( ) ;
146- responseMock . Setup ( r => r . Status ) . Returns ( 200 ) ;
147- responseMock . Setup ( r => r . ClientRequestId ) . Returns ( Guid . NewGuid ( ) . ToString ( ) ) ;
148-
149- _pages = new [ ]
150- {
151- Page < T > . FromValues ( list , continuationToken : null , response : responseMock . Object )
152- } ;
153- }
154-
155- public override async IAsyncEnumerable < Page < T > > AsPages ( string continuationToken = null , int ? pageSizeHint = null )
156- {
157- foreach ( var p in _pages )
158- {
159- yield return p ;
160- await Task . Yield ( ) ;
161- }
162- }
163- }
16448 }
165- }
49+ }
0 commit comments