@@ -25,9 +25,10 @@ public class EntraTokenCredentialTest
2525 protected string TokenResponse = string . Format ( TokenResponseTemplate , SampleToken , SampleTokenExpiry ) ;
2626
2727 private Mock < TokenCredential > _mockTokenCredential = null ! ;
28- private const string comunicationClientsEndpoint = "/access/entra/:exchangeAccessToken" ;
29- private const string communicationClientsScope = "https://communication.azure.com/clients/VoIP" ;
30- private const string teamsExtensionEndpoint = "/access/teamsPhone/:exchangeAccessToken" ;
28+ private const string communicationClientsEndpoint = "/access/entra/:exchangeAccessToken" ;
29+ private const string communicationClientsPrefix = "https://communication.azure.com/clients/" ;
30+ private const string communicationClientsScope = communicationClientsPrefix + "VoIP" ;
31+ private const string teamsExtensionEndpoint = "/access/teamsExtension/:exchangeAccessToken" ;
3132 private const string teamsExtensionScope = "https://auth.msft.communication.azure.com/TeamsExtension.ManageCalls" ;
3233 private string _resourceEndpoint = "https://myResource.communication.azure.com" ;
3334
@@ -123,13 +124,13 @@ public async Task EntraTokenCredential_GetToken_ReturnsToken(string[] scopes)
123124 }
124125 else
125126 {
126- Assert . AreEqual ( comunicationClientsEndpoint , mockTransport . SingleRequest . Uri . Path ) ;
127+ Assert . AreEqual ( communicationClientsEndpoint , mockTransport . SingleRequest . Uri . Path ) ;
127128 }
128129 _mockTokenCredential . Verify ( tc => tc . GetTokenAsync ( It . IsAny < TokenRequestContext > ( ) , It . IsAny < CancellationToken > ( ) ) , Times . Once ) ;
129130 }
130131
131132 [ Test ]
132- public async Task EntraTokenCredential_InitWithoutScopes_ReturnsComunicationClientsToken ( )
133+ public async Task EntraTokenCredential_InitWithoutScopes_ReturnsCommunicationClientsToken ( )
133134 {
134135 // Arrange
135136 var expiryTime = DateTimeOffset . Parse ( SampleTokenExpiry , null , System . Globalization . DateTimeStyles . RoundtripKind ) ;
@@ -143,7 +144,7 @@ public async Task EntraTokenCredential_InitWithoutScopes_ReturnsComunicationClie
143144 // Assert
144145 Assert . AreEqual ( SampleToken , token . Token ) ;
145146 Assert . AreEqual ( token . ExpiresOn , expiryTime ) ;
146- Assert . AreEqual ( comunicationClientsEndpoint , mockTransport . SingleRequest . Uri . Path ) ;
147+ Assert . AreEqual ( communicationClientsEndpoint , mockTransport . SingleRequest . Uri . Path ) ;
147148 _mockTokenCredential . Verify ( tc => tc . GetTokenAsync ( It . IsAny < TokenRequestContext > ( ) , It . IsAny < CancellationToken > ( ) ) , Times . Once ) ;
148149 }
149150
@@ -152,18 +153,20 @@ public async Task EntraTokenCredential_GetToken_InternalEntraTokenChangeInvalida
152153 {
153154 // Arrange
154155 var expiryTime = DateTimeOffset . Parse ( SampleTokenExpiry , null , System . Globalization . DateTimeStyles . RoundtripKind ) ;
155- var newToken = "newToken" ;
156156 var refreshOn = DateTimeOffset . Now ;
157+ _mockTokenCredential . Reset ( ) ;
157158 _mockTokenCredential
158159 . SetupSequence ( tc => tc . GetTokenAsync ( It . IsAny < TokenRequestContext > ( ) , It . IsAny < CancellationToken > ( ) ) )
159160 . ReturnsAsync ( new AccessToken ( "Entra token for call from constructor" , refreshOn ) )
160161 . ReturnsAsync ( new AccessToken ( "Entra token for the first getToken call token" , expiryTime ) ) ;
161162
162- var options = CreateEntraTokenCredentialOptions ( scopes ) ;
163+ var newToken = "newToken" ;
163164 var latestTokenResponse = string . Format ( TokenResponseTemplate , newToken , SampleTokenExpiry ) ;
164165 var mockTransport = CreateMockTransport ( new [ ] { CreateMockResponse ( 200 , TokenResponse ) , CreateMockResponse ( 200 , latestTokenResponse ) } ) ;
165- var entraTokenCredential = new EntraTokenCredential ( options , mockTransport ) ;
166+ var options = CreateEntraTokenCredentialOptions ( scopes ) ;
167+
166168 // Act
169+ var entraTokenCredential = new EntraTokenCredential ( options , mockTransport ) ;
167170 var token = await entraTokenCredential . GetTokenAsync ( CancellationToken . None ) ;
168171
169172 // Assert for cached tokens are updated
@@ -272,6 +275,38 @@ public void EntraTokenCredential_GetToken_ThrowsForInvalidScopes(string[] scopes
272275 StringAssert . Contains ( "Scopes validation failed. Ensure all scopes start with either" , ex ? . Message ) ;
273276 }
274277
278+ [ Test ]
279+ public async Task EntraTokenCredential_GetToken_NoIssueIfScopesChanged ( )
280+ {
281+ var scopes = new string [ ] { communicationClientsScope , communicationClientsPrefix + "Chat" } ;
282+
283+ // Arrange
284+ _mockTokenCredential . Reset ( ) ;
285+ var expiryTime = DateTimeOffset . Parse ( SampleTokenExpiry , null , System . Globalization . DateTimeStyles . RoundtripKind ) ;
286+ var refreshOn = DateTimeOffset . Now ;
287+ _mockTokenCredential
288+ . SetupSequence ( tc => tc . GetTokenAsync ( It . Is < TokenRequestContext > ( c => c . Scopes . SequenceEqual ( scopes ) ) , It . IsAny < CancellationToken > ( ) ) )
289+ . ReturnsAsync ( new AccessToken ( "Entra token for call from constructor" , refreshOn ) )
290+ . ReturnsAsync ( new AccessToken ( "Entra token for the first getToken call token" , expiryTime ) ) ;
291+
292+ var newToken = "newToken" ;
293+ var latestTokenResponse = string . Format ( TokenResponseTemplate , newToken , SampleTokenExpiry ) ;
294+ var mockTransport = CreateMockTransport ( new [ ] { CreateMockResponse ( 200 , TokenResponse ) , CreateMockResponse ( 200 , latestTokenResponse ) } ) ;
295+
296+ var options = CreateEntraTokenCredentialOptions ( ( string [ ] ) scopes . Clone ( ) ) ;
297+
298+ // Act
299+ var entraTokenCredential = new EntraTokenCredential ( options , mockTransport ) ;
300+
301+ // Change scopes
302+ options . Scopes [ 1 ] = teamsExtensionScope ;
303+ var token = await entraTokenCredential . GetTokenAsync ( CancellationToken . None ) ;
304+
305+ // Assert for cached tokens are updated
306+ Assert . AreEqual ( newToken , token . Token ) ;
307+ _mockTokenCredential . Verify ( tc => tc . GetTokenAsync ( It . IsAny < TokenRequestContext > ( ) , It . IsAny < CancellationToken > ( ) ) , Times . Exactly ( 2 ) ) ;
308+ }
309+
275310 private EntraCommunicationTokenCredentialOptions CreateEntraTokenCredentialOptions ( string [ ] scopes )
276311 {
277312 return new EntraCommunicationTokenCredentialOptions ( _resourceEndpoint , _mockTokenCredential . Object )
0 commit comments