Skip to content

Commit 55155ea

Browse files
deyaaeldeenDeyaaeldeen AlmahallawiCopilot
authored
Improve test coverage to ~100% for @azure/core-client (#38176)
## Changes ### New test files - base64.spec.ts (node + browser) - interfaceHelpers.spec.ts - operationHelpers.spec.ts - pipeline.spec.ts - utils.spec.ts - urlHelpers.spec.ts (expanded) ### Expanded existing tests - deserializationPolicy.spec.ts: error paths, XML parsing, header deserialization - serializationPolicy.spec.ts: body serialization edge cases, stream bodies - serializer.spec.ts: polymorphism, flattening, readonly, date/bytearray edge cases - serviceClient.spec.ts: auth, custom pipelines, error handling, base URI - authorizeRequestOnClaimChallenge.spec.ts: challenge parsing edge cases - authorizeRequestOnTenantChallenge.spec.ts: multi-call token flows ### Test quality improvements - Replaced mutable request capture with vi.fn() pattern - Replaced try/catch + assert.fail with rejects.toThrow() / toMatchObject() - Replaced calledOnce flags with vi.fn().mockImplementationOnce() - deepEqual on primitives changed to strictEqual - assert.equal(x, undefined) changed to assert.isUndefined(x) --------- Co-authored-by: Deyaaeldeen Almahallawi <deyaa@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 04a1144 commit 55155ea

15 files changed

Lines changed: 3968 additions & 240 deletions

sdk/core/core-client-rest/test/internal/clientHelpers.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ describe("clientHelpers", () => {
7575
);
7676
});
7777

78+
it("should not treat a non-string key property as a KeyCredential", () => {
79+
const pipeline = createDefaultPipeline(mockBaseUrl, { key: 123 } as any);
80+
const policies = pipeline.getOrderedPolicies();
81+
82+
assert.isUndefined(
83+
policies.find((p) => p.name === keyCredentialAuthenticationPolicyName),
84+
"pipeline should not have keyCredentialAuthenticationPolicyName for non-string key",
85+
);
86+
});
87+
7888
it("should create a default pipeline with TokenCredential", () => {
7989
const mockCredential: TokenCredential = {
8090
getToken: async () => ({ expiresOnTimestamp: 0, token: "mockToken" }),

sdk/core/core-client/test/internal/authorizeRequestOnClaimChallenge.spec.ts

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import {
1616
} from "../../src/authorizeRequestOnClaimChallenge.js";
1717
import { encodeString } from "../../src/base64.js";
1818

19+
const defaultRequest = () => createPipelineRequest({ url: "https://example.com" });
20+
1921
describe("authorizeRequestOnClaimChallenge", function () {
2022
it(`should try to get the access token if the response has a valid claims parameter on the WWW-Authenticate header`, async function () {
21-
const request = createPipelineRequest({ url: "https://example.com" });
23+
const request = defaultRequest();
2224
const getAccessTokenParameters: {
2325
scopes: string | string[];
2426
getTokenOptions: GetTokenOptions;
@@ -55,13 +57,13 @@ describe("authorizeRequestOnClaimChallenge", function () {
5557
scopes: ["https://endpoint/.default"],
5658
getTokenOptions: {
5759
claims: '{"access_token":{"nbf":{"essential":true, "value":"1603742800"}}}',
58-
} as GetTokenOptions,
60+
} satisfies GetTokenOptions,
5961
},
6062
]);
6163
});
6264

6365
it(`should try to get the access token with the parametrized scopes if the response has no scope property on the WWW-authenticate header`, async function () {
64-
const request = createPipelineRequest({ url: "https://example.com" });
66+
const request = defaultRequest();
6567
const getAccessTokenParameters: {
6668
scopes: string | string[];
6769
getTokenOptions: GetTokenOptions;
@@ -97,7 +99,7 @@ describe("authorizeRequestOnClaimChallenge", function () {
9799
scopes: ["https://parametrized-endpoint/.default"],
98100
getTokenOptions: {
99101
claims: '{"access_token":{"nbf":{"essential":true, "value":"1603742800"}}}',
100-
} as GetTokenOptions,
102+
} satisfies GetTokenOptions,
101103
},
102104
]);
103105
});
@@ -106,7 +108,7 @@ describe("authorizeRequestOnClaimChallenge", function () {
106108
// In Python, padding has to be added at the end if the size of the base64 string is not a multiple of 4.
107109
// In JavaScript, the padding is added automatically.
108110

109-
const request = createPipelineRequest({ url: "https://example.com" });
111+
const request = defaultRequest();
110112
const getAccessTokenParameters: {
111113
scopes: string | string[];
112114
getTokenOptions: GetTokenOptions;
@@ -143,13 +145,13 @@ describe("authorizeRequestOnClaimChallenge", function () {
143145
scopes: ["https://parametrized-endpoint/.default"],
144146
getTokenOptions: {
145147
claims: '{"access_token":{"nbf":{"essential":true, "value":"1603742800"}}}',
146-
} as GetTokenOptions,
148+
} satisfies GetTokenOptions,
147149
},
148150
]);
149151
});
150152

151153
it(`should return false if getAccessToken is called and if it doesn't return an access token`, async function () {
152-
const request = createPipelineRequest({ url: "https://example.com" });
154+
const request = defaultRequest();
153155
const getAccessTokenParameters: {
154156
scopes: string | string[];
155157
getTokenOptions: GetTokenOptions;
@@ -182,13 +184,13 @@ describe("authorizeRequestOnClaimChallenge", function () {
182184
scopes: ["https://parametrized-endpoint/.default"],
183185
getTokenOptions: {
184186
claims: '{"access_token":{"nbf":{"essential":true, "value":"1603742800"}}}',
185-
} as GetTokenOptions,
187+
} satisfies GetTokenOptions,
186188
},
187189
]);
188190
});
189191

190192
it(`should return false if the response has an invalid claims parameter on the WWW-Authenticate header`, async function () {
191-
const request = createPipelineRequest({ url: "https://example.com" });
193+
const request = defaultRequest();
192194
const getAccessTokenParameters: {
193195
scopes: string | string[];
194196
getTokenOptions: GetTokenOptions;
@@ -216,7 +218,7 @@ describe("authorizeRequestOnClaimChallenge", function () {
216218
});
217219

218220
it(`should return false if the response has no WWW-Authenticate header`, async function () {
219-
const request = createPipelineRequest({ url: "https://example.com" });
221+
const request = defaultRequest();
220222
const getAccessTokenParameters: {
221223
scopes: string | string[];
222224
getTokenOptions: GetTokenOptions;
@@ -283,7 +285,7 @@ describe("authorizeRequestOnClaimChallenge", function () {
283285
}),
284286
};
285287

286-
const pipelineRequest = createPipelineRequest({ url: "https://example.com" });
288+
const pipelineRequest = defaultRequest();
287289
const responses: PipelineResponse[] = [
288290
{
289291
headers: createHttpHeaders({
@@ -353,7 +355,7 @@ describe("authorizeRequestOnClaimChallenge", function () {
353355
});
354356

355357
it(`a custom logger should log a reasonable message if no challenge is received`, async function () {
356-
const request = createPipelineRequest({ url: "https://example.com" });
358+
const request = defaultRequest();
357359
const getAccessTokenParameters: {
358360
scopes: string | string[];
359361
getTokenOptions: GetTokenOptions;
@@ -391,7 +393,7 @@ describe("authorizeRequestOnClaimChallenge", function () {
391393
});
392394

393395
it(`a custom logger should log a reasonable message if a bad challenge is received`, async function () {
394-
const request = createPipelineRequest({ url: "https://example.com" });
396+
const request = defaultRequest();
395397
const getAccessTokenParameters: {
396398
scopes: string | string[];
397399
getTokenOptions: GetTokenOptions;
@@ -436,3 +438,62 @@ describe("authorizeRequestOnClaimChallenge", function () {
436438
);
437439
});
438440
});
441+
442+
describe("authorizeRequestOnClaimChallenge", () => {
443+
it("should handle malformed WWW-Authenticate header (no claims)", async () => {
444+
const request = defaultRequest();
445+
const result = await authorizeRequestOnClaimChallenge({
446+
async getAccessToken() {
447+
return { token: "token", expiresOnTimestamp: Date.now() + 3600000 };
448+
},
449+
scopes: [],
450+
response: {
451+
headers: createHttpHeaders({
452+
"WWW-Authenticate": 'Bearer realm="test"',
453+
}),
454+
request,
455+
status: 401,
456+
},
457+
request,
458+
});
459+
assert.isFalse(result);
460+
});
461+
462+
it("should handle missing WWW-Authenticate header", async () => {
463+
const request = defaultRequest();
464+
const result = await authorizeRequestOnClaimChallenge({
465+
async getAccessToken() {
466+
return { token: "token", expiresOnTimestamp: Date.now() + 3600000 };
467+
},
468+
scopes: [],
469+
response: {
470+
headers: createHttpHeaders(),
471+
request,
472+
status: 401,
473+
},
474+
request,
475+
});
476+
assert.isFalse(result);
477+
});
478+
});
479+
480+
describe("authorizeRequestOnClaimChallenge", () => {
481+
it("should handle completely unparseable WWW-Authenticate value", async () => {
482+
const request = defaultRequest();
483+
const result = await authorizeRequestOnClaimChallenge({
484+
async getAccessToken() {
485+
return { token: "token", expiresOnTimestamp: Date.now() + 3600000 };
486+
},
487+
scopes: [],
488+
response: {
489+
headers: createHttpHeaders({
490+
"WWW-Authenticate": "NotBearer gibberish",
491+
}),
492+
request,
493+
status: 401,
494+
},
495+
request,
496+
});
497+
assert.isFalse(result);
498+
});
499+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { describe, it, assert } from "vitest";
5+
import { encodeByteArray } from "../../src/base64.js";
6+
7+
describe("base64", () => {
8+
it("should handle Uint8Array input in encodeByteArray", () => {
9+
const arr = new Uint8Array([72, 101, 108, 108, 111]);
10+
const result = encodeByteArray(arr);
11+
assert.strictEqual(result, "SGVsbG8=");
12+
});
13+
});

0 commit comments

Comments
 (0)