Skip to content

Commit ca3a141

Browse files
gladjohnCopilot
andcommitted
Add 119 unit tests to improve code coverage
Coverage improvements for Microsoft.Identity.Client: - Line coverage: 82.58% -> 84.20% (+1.62%, +387 lines) - Branch coverage: 72.91% -> 75.09% (+2.18%, +128 branches) New test files: - ConcurrentHashSetTests: comprehensive tests for thread-safe collection - Base64UrlHelpersExtendedTests: edge cases for encoding/decoding - IdTokenExtendedTests: null/empty/malformed JWT, claim mapping - WsTrustResponseExtendedTests: SOAP fault parsing, SAML selection - AuthorizationResultExtendedTests: URI/PostData parsing edge cases - ThrottlingCacheExtendedTests: cleanup, clear, miss paths - TokenCacheNotificationArgsExtendedTests: constructor overloads - TraceTelemetryConfigTests: basic config properties Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2007082 commit ca3a141

File tree

8 files changed

+1585
-0
lines changed

8 files changed

+1585
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using Microsoft.Identity.Client;
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
11+
namespace Microsoft.Identity.Test.Unit.CacheTests
12+
{
13+
[TestClass]
14+
public class TokenCacheNotificationArgsExtendedTests : TestBase
15+
{
16+
[TestMethod]
17+
public void Constructor_BasicOverload_SetsAllProperties()
18+
{
19+
var cts = new CancellationTokenSource();
20+
var expiry = DateTimeOffset.UtcNow.AddHours(1);
21+
22+
var args = new TokenCacheNotificationArgs(
23+
tokenCache: null,
24+
clientId: "client-id",
25+
account: null,
26+
hasStateChanged: true,
27+
isApplicationCache: true,
28+
suggestedCacheKey: "cache-key",
29+
hasTokens: true,
30+
suggestedCacheExpiry: expiry,
31+
cancellationToken: cts.Token);
32+
33+
Assert.AreEqual("client-id", args.ClientId);
34+
Assert.IsTrue(args.HasStateChanged);
35+
Assert.IsTrue(args.IsApplicationCache);
36+
Assert.AreEqual("cache-key", args.SuggestedCacheKey);
37+
Assert.IsTrue(args.HasTokens);
38+
Assert.AreEqual(expiry, args.SuggestedCacheExpiry);
39+
Assert.AreEqual(cts.Token, args.CancellationToken);
40+
Assert.IsNull(args.TokenCache);
41+
Assert.IsNull(args.Account);
42+
}
43+
44+
[TestMethod]
45+
public void Constructor_WithCorrelationId_SetsCorrelationId()
46+
{
47+
var correlationId = Guid.NewGuid();
48+
var args = new TokenCacheNotificationArgs(
49+
tokenCache: null,
50+
clientId: "client-id",
51+
account: null,
52+
hasStateChanged: false,
53+
isApplicationCache: false,
54+
suggestedCacheKey: null,
55+
hasTokens: false,
56+
suggestedCacheExpiry: null,
57+
cancellationToken: CancellationToken.None,
58+
correlationId: correlationId);
59+
60+
Assert.AreEqual(correlationId, args.CorrelationId);
61+
}
62+
63+
[TestMethod]
64+
public void Constructor_WithScopesAndTenant_SetsFields()
65+
{
66+
var scopes = new[] { "User.Read", "Mail.Read" };
67+
var args = new TokenCacheNotificationArgs(
68+
tokenCache: null,
69+
clientId: "client-id",
70+
account: null,
71+
hasStateChanged: false,
72+
isApplicationCache: false,
73+
suggestedCacheKey: null,
74+
hasTokens: false,
75+
suggestedCacheExpiry: null,
76+
cancellationToken: CancellationToken.None,
77+
correlationId: Guid.Empty,
78+
requestScopes: scopes,
79+
requestTenantId: "tenant-id");
80+
81+
CollectionAssert.AreEqual(scopes, new List<string>(args.RequestScopes));
82+
Assert.AreEqual("tenant-id", args.RequestTenantId);
83+
}
84+
85+
[TestMethod]
86+
public void Constructor_FullOverload_SetsAllFields()
87+
{
88+
var correlationId = Guid.NewGuid();
89+
var scopes = new[] { "scope1" };
90+
var expiry = DateTimeOffset.UtcNow.AddMinutes(30);
91+
92+
var args = new TokenCacheNotificationArgs(
93+
tokenCache: null,
94+
clientId: "cid",
95+
account: null,
96+
hasStateChanged: true,
97+
isApplicationCache: true,
98+
suggestedCacheKey: "key",
99+
hasTokens: true,
100+
suggestedCacheExpiry: expiry,
101+
cancellationToken: CancellationToken.None,
102+
correlationId: correlationId,
103+
requestScopes: scopes,
104+
requestTenantId: "tid",
105+
identityLogger: null,
106+
piiLoggingEnabled: true);
107+
108+
Assert.AreEqual("cid", args.ClientId);
109+
Assert.IsTrue(args.HasStateChanged);
110+
Assert.IsTrue(args.IsApplicationCache);
111+
Assert.AreEqual("key", args.SuggestedCacheKey);
112+
Assert.IsTrue(args.HasTokens);
113+
Assert.AreEqual(expiry, args.SuggestedCacheExpiry);
114+
Assert.AreEqual(correlationId, args.CorrelationId);
115+
Assert.AreEqual("tid", args.RequestTenantId);
116+
Assert.IsTrue(args.PiiLoggingEnabled);
117+
}
118+
119+
[TestMethod]
120+
public void Constructor_FullOverload_DefaultsTelemetryData_WhenNull()
121+
{
122+
var args = new TokenCacheNotificationArgs(
123+
tokenCache: null,
124+
clientId: "cid",
125+
account: null,
126+
hasStateChanged: false,
127+
isApplicationCache: false,
128+
suggestedCacheKey: null,
129+
hasTokens: false,
130+
suggestedCacheExpiry: null,
131+
cancellationToken: CancellationToken.None,
132+
correlationId: Guid.Empty,
133+
requestScopes: null,
134+
requestTenantId: null,
135+
identityLogger: null,
136+
piiLoggingEnabled: false,
137+
telemetryData: null);
138+
139+
Assert.IsNotNull(args.TelemetryData, "TelemetryData should be defaulted when null");
140+
}
141+
142+
[TestMethod]
143+
public void HasStateChanged_CanBeSetInternally()
144+
{
145+
var args = new TokenCacheNotificationArgs(
146+
tokenCache: null,
147+
clientId: "cid",
148+
account: null,
149+
hasStateChanged: false,
150+
isApplicationCache: false,
151+
suggestedCacheKey: null,
152+
hasTokens: false,
153+
suggestedCacheExpiry: null,
154+
cancellationToken: CancellationToken.None);
155+
156+
Assert.IsFalse(args.HasStateChanged);
157+
args.HasStateChanged = true;
158+
Assert.IsTrue(args.HasStateChanged);
159+
}
160+
}
161+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
5+
using System;
6+
using System.Text;
7+
using Microsoft.Identity.Client;
8+
using Microsoft.Identity.Client.Internal;
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
11+
namespace Microsoft.Identity.Test.Unit.CoreTests
12+
{
13+
[TestClass]
14+
public class IdTokenExtendedTests : TestBase
15+
{
16+
[TestMethod]
17+
public void Parse_Null_ReturnsNull()
18+
{
19+
Assert.IsNull(IdToken.Parse(null));
20+
}
21+
22+
[TestMethod]
23+
public void Parse_EmptyString_ReturnsNull()
24+
{
25+
Assert.IsNull(IdToken.Parse(string.Empty));
26+
}
27+
28+
[TestMethod]
29+
public void Parse_SingleSegment_ThrowsMsalClientException()
30+
{
31+
Assert.Throws<MsalClientException>(() => IdToken.Parse("singlesegment"));
32+
}
33+
34+
[TestMethod]
35+
public void Parse_SingleSegment_HasCorrectErrorCode()
36+
{
37+
try
38+
{
39+
IdToken.Parse("singlesegment");
40+
Assert.Fail("Should have thrown");
41+
}
42+
catch (MsalClientException ex)
43+
{
44+
Assert.AreEqual(MsalError.InvalidJwtError, ex.ErrorCode);
45+
}
46+
}
47+
48+
[TestMethod]
49+
public void GetUniqueId_UsesObjectId_WhenPresent()
50+
{
51+
string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
52+
@"{""oid"":""object-id-123"",""sub"":""subject-456""}"));
53+
string jwt = $"header.{payload}.signature";
54+
55+
var token = IdToken.Parse(jwt);
56+
Assert.AreEqual("object-id-123", token.GetUniqueId());
57+
}
58+
59+
[TestMethod]
60+
public void GetUniqueId_FallsBackToSubject_WhenObjectIdMissing()
61+
{
62+
string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
63+
@"{""sub"":""subject-456""}"));
64+
string jwt = $"header.{payload}.signature";
65+
66+
var token = IdToken.Parse(jwt);
67+
Assert.IsNull(token.ObjectId);
68+
Assert.AreEqual("subject-456", token.GetUniqueId());
69+
}
70+
71+
[TestMethod]
72+
public void Parse_MapsAllStandardClaims()
73+
{
74+
string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
75+
@"{""oid"":""oid1"",""sub"":""sub1"",""tid"":""tid1"",""preferred_username"":""user@example.com"",""name"":""Test User"",""email"":""test@example.com"",""upn"":""user@domain.com"",""given_name"":""Test"",""family_name"":""User""}"));
76+
string jwt = $"header.{payload}.signature";
77+
78+
var token = IdToken.Parse(jwt);
79+
80+
Assert.AreEqual("oid1", token.ObjectId);
81+
Assert.AreEqual("sub1", token.Subject);
82+
Assert.AreEqual("tid1", token.TenantId);
83+
Assert.AreEqual("user@example.com", token.PreferredUsername);
84+
Assert.AreEqual("Test User", token.Name);
85+
Assert.AreEqual("test@example.com", token.Email);
86+
Assert.AreEqual("user@domain.com", token.Upn);
87+
Assert.AreEqual("Test", token.GivenName);
88+
Assert.AreEqual("User", token.FamilyName);
89+
}
90+
91+
[TestMethod]
92+
public void Parse_ClaimsPrincipal_IsPopulated()
93+
{
94+
string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
95+
@"{""iss"":""https://login.example.com/tenant/v2.0"",""name"":""Test User""}"));
96+
string jwt = $"header.{payload}.signature";
97+
98+
var token = IdToken.Parse(jwt);
99+
100+
Assert.IsNotNull(token.ClaimsPrincipal);
101+
Assert.IsNotNull(token.ClaimsPrincipal.Identity);
102+
}
103+
104+
[TestMethod]
105+
public void Parse_NoIssuer_UsesLocalAuthority()
106+
{
107+
string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
108+
@"{""name"":""Test User""}"));
109+
string jwt = $"header.{payload}.signature";
110+
111+
var token = IdToken.Parse(jwt);
112+
113+
Assert.IsNotNull(token.ClaimsPrincipal);
114+
var nameClaim = token.ClaimsPrincipal.FindFirst("name");
115+
Assert.IsNotNull(nameClaim);
116+
Assert.AreEqual("LOCAL AUTHORITY", nameClaim.Issuer);
117+
}
118+
119+
[TestMethod]
120+
public void Parse_NullClaimValue_CreatesJsonNullClaim()
121+
{
122+
string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
123+
@"{""iss"":""issuer"",""custom_claim"":null}"));
124+
string jwt = $"header.{payload}.signature";
125+
126+
var token = IdToken.Parse(jwt);
127+
128+
Assert.IsNotNull(token.ClaimsPrincipal);
129+
var customClaim = token.ClaimsPrincipal.FindFirst("custom_claim");
130+
Assert.IsNotNull(customClaim);
131+
Assert.AreEqual(string.Empty, customClaim.Value);
132+
}
133+
134+
[TestMethod]
135+
public void Parse_InvalidBase64Payload_Throws()
136+
{
137+
// Valid JWT structure but payload is not valid base64 - may throw MsalClientException or FormatException
138+
try
139+
{
140+
IdToken.Parse("header.!!!invalid!!!.signature");
141+
Assert.Fail("Should have thrown an exception");
142+
}
143+
catch (MsalClientException)
144+
{
145+
// Expected - JSON parse error
146+
}
147+
catch (FormatException)
148+
{
149+
// Expected - invalid base64
150+
}
151+
}
152+
153+
[TestMethod]
154+
public void Parse_MinimalToken_NoOptionalClaims()
155+
{
156+
string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(@"{}"));
157+
string jwt = $"header.{payload}.signature";
158+
159+
var token = IdToken.Parse(jwt);
160+
161+
Assert.IsNull(token.ObjectId);
162+
Assert.IsNull(token.Subject);
163+
Assert.IsNull(token.TenantId);
164+
Assert.IsNull(token.PreferredUsername);
165+
Assert.IsNull(token.Name);
166+
Assert.IsNull(token.Email);
167+
Assert.IsNull(token.Upn);
168+
Assert.IsNull(token.GivenName);
169+
Assert.IsNull(token.FamilyName);
170+
Assert.IsNull(token.GetUniqueId());
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)