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