1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT License.
3
3
4
+ using System ;
4
5
using System . Collections . Generic ;
5
6
using 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 ;
6
16
using NUnit . Framework ;
7
17
8
18
namespace Microsoft . Azure . WebJobs . Extensions . Storage . Blobs . Listeners
9
19
{
10
20
public class BlobLogListenerTests
11
21
{
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
+
12
86
[ Test ]
13
87
public void GetPathsForValidBlobWrites_Returns_ValidBlobWritesOnly ( )
14
88
{
15
89
StorageAnalyticsLogEntry [ ] entries = new [ ]
16
90
{
17
- // This is a valid write entry with a valid path
18
91
new StorageAnalyticsLogEntry
19
92
{
20
93
ServiceType = StorageServiceType . Blob ,
21
94
OperationType = StorageServiceOperationType . PutBlob ,
22
95
RequestedObjectKey = @"/storagesample/sample-container/""0x8D199A96CB71468""/sample-blob.txt"
23
96
} ,
24
-
25
- // This is an invalid path and will be filtered out
26
97
new StorageAnalyticsLogEntry
27
98
{
28
99
ServiceType = StorageServiceType . Blob ,
29
100
OperationType = StorageServiceOperationType . PutBlob ,
30
101
RequestedObjectKey = "/"
31
102
} ,
32
-
33
- // This does not constitute a write and will be filtered out
34
103
new StorageAnalyticsLogEntry
35
104
{
36
105
ServiceType = StorageServiceType . Blob ,
@@ -45,5 +114,52 @@ public void GetPathsForValidBlobWrites_Returns_ValidBlobWritesOnly()
45
114
Assert . AreEqual ( "sample-container" , singlePath . ContainerName ) ;
46
115
Assert . AreEqual ( @"""0x8D199A96CB71468""/sample-blob.txt" , singlePath . BlobName ) ;
47
116
}
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
+ }
48
164
}
49
- }
165
+ }
0 commit comments