1
+ // ----------------------------------------------------------------------------------
2
+ //
3
+ // Copyright Microsoft Corporation
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ // ----------------------------------------------------------------------------------
14
+ using Microsoft . Azure . Commands . Common . Authentication . Abstractions ;
15
+ using Microsoft . Azure . Commands . Common . Authentication . Abstractions . Interfaces ;
16
+
17
+ using Moq ;
18
+
19
+ using System ;
20
+ using System . Collections . Generic ;
21
+ using System . Linq ;
22
+ using System . Threading . Tasks ;
23
+
24
+ using Xunit ;
25
+
26
+ namespace Authentication . Abstractions . Test
27
+ {
28
+ public class AuthenticationTelemetryTest
29
+ {
30
+ // Theory for PushTelemetryRecord tests
31
+ [ Theory ]
32
+ [ InlineData ( true , true , true , 1 , 1 , 0 ) ] // Valid context, valid record -> success
33
+ [ InlineData ( false , true , false , 0 , 0 , 1 ) ] // Invalid context, valid record -> failure
34
+ [ InlineData ( true , false , false , 0 , 0 , 1 ) ] // Valid context, null record -> failure
35
+ [ InlineData ( null , true , false , 0 , 0 , 1 ) ] // Null context, valid record -> failure
36
+ public void PushTelemetryRecord_Tests ( bool ? isContextValid , bool hasRecord , bool expectedResult ,
37
+ int expectedKeysCurrent , int expectedKeysAll , int expectedEmptyCount )
38
+ {
39
+ // Arrange
40
+ var telemetry = new AuthenticationTelemetry ( ) ;
41
+ ICmdletContext context = null ;
42
+ AuthTelemetryRecord record = hasRecord ? new AuthTelemetryRecord ( ) : null ;
43
+
44
+ if ( isContextValid . HasValue )
45
+ {
46
+ var mockContext = new Mock < ICmdletContext > ( ) ;
47
+ mockContext . Setup ( c => c . IsValid ) . Returns ( isContextValid . Value ) ;
48
+ if ( isContextValid . Value )
49
+ {
50
+ mockContext . Setup ( c => c . CmdletId ) . Returns ( "TestCmdlet" ) ;
51
+ }
52
+ context = mockContext . Object ;
53
+ }
54
+
55
+ // Act
56
+ var result = telemetry . PushDataRecord ( context , record ) ;
57
+
58
+ // Assert
59
+ Assert . Equal ( expectedResult , result ) ;
60
+ }
61
+
62
+ // Test data for PopTelemetryRecord tests
63
+ public static IEnumerable < object [ ] > PopTelemetryRecordTestData =>
64
+ new List < object [ ] >
65
+ {
66
+ // Parameters: isContextValid, cmdletId, pushBeforePop, expectedNotNull, expectedKeysCount, expectedAllCount, expectedEmptyCount, expectedKeyNotFoundCount
67
+ new object [ ] { true , "TestCmdlet" , true , true , 0 , 1 , 0 , 0 } , // Valid context with existing record
68
+ new object [ ] { null , null , false , false , 0 , 0 , 1 , 0 } , // Null context
69
+ new object [ ] { false , null , false , false , 0 , 0 , 1 , 0 } , // Invalid context
70
+ new object [ ] { true , "TestCmdlet" , false , false , 0 , 0 , 0 , 1 } // Valid context with non-existent key
71
+ } ;
72
+
73
+ [ Theory ]
74
+ [ MemberData ( nameof ( PopTelemetryRecordTestData ) ) ]
75
+ public void PopTelemetryRecord_Tests ( bool ? isContextValid , string cmdletId , bool pushBeforePop ,
76
+ bool expectedNotNull , int expectedKeysCount , int expectedAllCount ,
77
+ int expectedEmptyCount , int expectedKeyNotFoundCount )
78
+ {
79
+ // Arrange
80
+ var telemetry = new AuthenticationTelemetry ( ) ;
81
+ ICmdletContext context = null ;
82
+
83
+ if ( isContextValid . HasValue )
84
+ {
85
+ var mockContext = new Mock < ICmdletContext > ( ) ;
86
+ mockContext . Setup ( c => c . IsValid ) . Returns ( isContextValid . Value ) ;
87
+ if ( ! string . IsNullOrEmpty ( cmdletId ) )
88
+ {
89
+ mockContext . Setup ( c => c . CmdletId ) . Returns ( cmdletId ) ;
90
+ }
91
+ context = mockContext . Object ;
92
+ }
93
+
94
+ // Push a record first if needed
95
+ if ( pushBeforePop && context != null )
96
+ {
97
+ var record = new AuthTelemetryRecord { TokenCredentialName = "TestCredential" } ;
98
+ Assert . True ( telemetry . PushDataRecord ( context , record ) ) ;
99
+ }
100
+
101
+ // Act
102
+ var result = telemetry . PopDataRecords ( context ) ;
103
+
104
+ // Assert
105
+ Assert . Equal ( expectedNotNull , result != null ) ;
106
+ if ( expectedNotNull )
107
+ {
108
+ Assert . Single ( result ) ;
109
+ Assert . Equal ( "TestCredential" , result . FirstOrDefault ( ) ? . TokenCredentialName ) ;
110
+ }
111
+ }
112
+
113
+ // Test data for GetTelemetryRecord tests
114
+ public static IEnumerable < object [ ] > GetTelemetryRecordTestData =>
115
+ new List < object [ ] >
116
+ {
117
+ // Parameters: isContextValid, cmdletId, recordCount, expectedNotNull
118
+ new object [ ] { true , "TestCmdlet" , 1 , true } , // Valid context with single record
119
+ new object [ ] { true , "TestCmdlet" , 3 , true } , // Valid context with multiple records
120
+ new object [ ] { null , null , 0 , false } , // Null context
121
+ new object [ ] { false , null , 0 , false } , // Invalid context
122
+ new object [ ] { true , "TestCmdlet" , 0 , false } // Valid context with no records
123
+ } ;
124
+
125
+ [ Theory ]
126
+ [ MemberData ( nameof ( GetTelemetryRecordTestData ) ) ]
127
+ public void GetTelemetryRecord_Tests ( bool ? isContextValid , string cmdletId , int recordCount ,
128
+ bool expectedNotNull )
129
+ {
130
+ // Arrange
131
+ var telemetry = new AuthenticationTelemetry ( ) ;
132
+ ICmdletContext context = null ;
133
+
134
+ if ( isContextValid . HasValue )
135
+ {
136
+ var mockContext = new Mock < ICmdletContext > ( ) ;
137
+ mockContext . Setup ( c => c . IsValid ) . Returns ( isContextValid . Value ) ;
138
+ if ( ! string . IsNullOrEmpty ( cmdletId ) )
139
+ {
140
+ mockContext . Setup ( c => c . CmdletId ) . Returns ( cmdletId ) ;
141
+ }
142
+ context = mockContext . Object ;
143
+ }
144
+
145
+ // Push records if needed
146
+ for ( int i = 0 ; i < recordCount ; i ++ )
147
+ {
148
+ var record = new AuthTelemetryRecord { TokenCredentialName = $ "TestCredential{ i } " } ;
149
+ Assert . True ( telemetry . PushDataRecord ( context , record ) ) ;
150
+ }
151
+
152
+ // Act
153
+ var result = telemetry . GetTelemetryRecord ( context ) ;
154
+
155
+ // Assert
156
+ Assert . Equal ( expectedNotNull , result != null ) ;
157
+
158
+ if ( expectedNotNull )
159
+ {
160
+ // Verify the AuthenticationTelemetryData contains our records
161
+ Assert . NotNull ( result . Primary ) ;
162
+ Assert . Equal ( "TestCredential0" , result . Primary . TokenCredentialName ) ;
163
+
164
+ if ( recordCount > 1 )
165
+ {
166
+ Assert . NotEmpty ( result . Secondary ) ;
167
+ Assert . Equal ( recordCount - 1 , result . Secondary . Count ) ;
168
+
169
+ // Verify each record in the tail
170
+ for ( int i = 1 ; i < recordCount ; i ++ )
171
+ {
172
+ Assert . Equal ( $ "TestCredential{ i } ", result . Secondary [ i - 1 ] . TokenCredentialName ) ;
173
+ }
174
+ }
175
+ else
176
+ {
177
+ Assert . Empty ( result . Secondary ) ;
178
+ }
179
+ }
180
+ }
181
+ [ Fact ]
182
+ public void TelemetryRecord_ConcurrentTests ( )
183
+ {
184
+ // Arrange
185
+ var telemetry = new AuthenticationTelemetry ( ) ;
186
+ const int pusherThreadCount = 10 ;
187
+
188
+ // Create delegate to push records and get telemetry data
189
+ Func < ICmdletContext , AuthenticationTelemetryData > PushAndGetFromMultipleThreads = ( context ) =>
190
+ {
191
+ // Create tasks for pushing records (10 threads)
192
+ var pushTasks = new Task [ pusherThreadCount ] ;
193
+ for ( int t = 0 ; t < pusherThreadCount ; t ++ )
194
+ {
195
+ var threadId = t ;
196
+ pushTasks [ t ] = Task . Run ( ( ) =>
197
+ {
198
+ // Each thread pushes one unique record
199
+ var record = new AuthTelemetryRecord { TokenCredentialName = $ "TestCredential-{ context . CmdletId } -{ threadId } " } ;
200
+ Assert . True ( telemetry . PushDataRecord ( context , record ) ) ;
201
+ } ) ;
202
+ }
203
+ Task . WaitAll ( pushTasks ) ; // Wait for all push tasks to complete
204
+ return telemetry . GetTelemetryRecord ( context ) ;
205
+ } ;
206
+
207
+ // Create two contexts
208
+ var mockContext1 = new Mock < ICmdletContext > ( ) ;
209
+ mockContext1 . Setup ( c => c . IsValid ) . Returns ( true ) ;
210
+ mockContext1 . Setup ( c => c . CmdletId ) . Returns ( "TestCmdlet1" ) ;
211
+ var context1 = mockContext1 . Object ;
212
+
213
+ var mockContext2 = new Mock < ICmdletContext > ( ) ;
214
+ mockContext2 . Setup ( c => c . IsValid ) . Returns ( true ) ;
215
+ mockContext2 . Setup ( c => c . CmdletId ) . Returns ( "TestCmdlet2" ) ;
216
+ var context2 = mockContext2 . Object ;
217
+
218
+ // Act
219
+ // Run tasks in parallel
220
+ var task1 = Task < AuthenticationTelemetryData > . Run ( ( ) => PushAndGetFromMultipleThreads ( context1 ) ) ;
221
+ var task2 = Task < AuthenticationTelemetryData > . Run ( ( ) => PushAndGetFromMultipleThreads ( context2 ) ) ;
222
+
223
+ // Wait for both tasks to complete
224
+ Task . WaitAll ( task1 , task2 ) ;
225
+
226
+ // Get results
227
+ var results1 = task1 . Result ;
228
+ var results2 = task2 . Result ;
229
+
230
+ // Assert
231
+ // Check that we have results from both contexts
232
+ Assert . NotNull ( results1 ) ;
233
+ Assert . True ( results1 . Primary ? . TokenCredentialName . StartsWith ( "TestCredential-TestCmdlet1" ) ) ;
234
+ Assert . Equal ( 9 , results1 . Secondary ? . Count ) ;
235
+ Assert . NotNull ( results2 ) ;
236
+ Assert . True ( results2 . Primary ? . TokenCredentialName . StartsWith ( "TestCredential-TestCmdlet2" ) ) ;
237
+ Assert . Equal ( 9 , results2 . Secondary ? . Count ) ;
238
+
239
+ // Verify all records were retrieved (nothing left)
240
+ Assert . Null ( telemetry . GetTelemetryRecord ( context1 ) ) ;
241
+ Assert . Null ( telemetry . GetTelemetryRecord ( context2 ) ) ;
242
+ }
243
+ }
244
+ }
0 commit comments