Skip to content

Commit 49bc828

Browse files
deyaaeldeenDeyaaeldeen AlmahallawiCopilot
authored
Improve test coverage to ~100% for @azure/core-amqp (Azure#38174)
## Changes ### New test files - checkNetworkConnection.spec.ts (browser) - checkNetworkMocked.spec.ts (node, mocked network) - errors.spec.ts (browser) - hmacSha256.spec.ts (browser) - retryNetworkDown.spec.ts (node) - runtimeInfo.spec.ts (browser) - typeGuards.spec.ts - utils.spec.ts ### Expanded existing tests - cbs.spec.ts: connection error paths, session close errors - context.spec.ts: extracted properties/webSocketOptions into typed consts - errors.spec.ts: translate edge cases, mapping coverage - lock.spec.ts: timeout and release paths - message.spec.ts: header/properties encoding edge cases - pipeline.spec.ts: policy ordering - requestResponse.spec.ts: retry, timeout, cleanup callbacks - retry.spec.ts: all retry modes and error paths - tokenProvider.spec.ts: token refresh edge cases ### Test quality improvements - Replaced hand-rolled counters with vi.fn() + toHaveBeenCalledTimes() - Replaced boolean tracking flags with vi.fn() + toHaveBeenCalled() - Replaced try/catch + assert.fail with rejects.toThrow() - Removed unnecessary as any casts - Extracted non-null assertions into typed consts --------- Co-authored-by: Deyaaeldeen Almahallawi <deyaa@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c4dc674 commit 49bc828

18 files changed

Lines changed: 1746 additions & 889 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { describe, it, assert } from "vitest";
5+
import { checkNetworkConnection } from "../../../src/util/checkNetworkConnection.common.js";
6+
7+
describe("checkNetworkConnection (browser)", function () {
8+
it("returns a boolean reflecting navigator.onLine", async function () {
9+
const result = await checkNetworkConnection("hostname.example.com");
10+
assert.isBoolean(result);
11+
// In a browser test environment, navigator.onLine should be true
12+
assert.equal(result, self.navigator.onLine);
13+
});
14+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { describe, it, assert } from "vitest";
5+
import { translate, MessagingError } from "../../../src/errors.js";
6+
7+
describe("translate - isBrowserWebsocketError (browser)", function () {
8+
it("translates a WebSocket error event into a MessagingError", function () {
9+
const ws = Object.create(WebSocket.prototype);
10+
const errorEvent = new Event("error");
11+
Object.defineProperty(errorEvent, "target", { value: ws, writable: false });
12+
13+
const result = translate(errorEvent);
14+
15+
assert.instanceOf(result, MessagingError);
16+
assert.equal(result.code, "ServiceCommunicationError");
17+
assert.isFalse(result.retryable);
18+
assert.include(result.message, "Websocket");
19+
});
20+
21+
it("does not treat a plain error as a browser websocket error", function () {
22+
const plainError = new Error("not a websocket error");
23+
const result = translate(plainError);
24+
25+
// A plain Error should be returned as-is, not wrapped as ServiceCommunicationError
26+
assert.equal(result, plainError);
27+
});
28+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { describe, it, assert } from "vitest";
5+
import { signString } from "../../../src/util/hmacSha256.common.js";
6+
7+
describe("signString (browser - Web Crypto)", function () {
8+
it("produces a URL-encoded base64 HMAC-SHA256 signature", async function () {
9+
const signature = await signString("testKey", "testMessage");
10+
assert.strictEqual(signature, "8N7PlLvnGgnE2gFU7%2BAkSxmAc02cXFkOLlFD5gTuOjo%3D");
11+
assert.strictEqual(
12+
decodeURIComponent(signature),
13+
"8N7PlLvnGgnE2gFU7+AkSxmAc02cXFkOLlFD5gTuOjo=",
14+
);
15+
});
16+
17+
it("returns consistent results for the same inputs", async function () {
18+
const sig1 = await signString("key", "data");
19+
const sig2 = await signString("key", "data");
20+
assert.equal(sig1, sig2);
21+
});
22+
23+
it("returns different results for different keys", async function () {
24+
const sig1 = await signString("key1", "data");
25+
const sig2 = await signString("key2", "data");
26+
assert.notEqual(sig1, sig2);
27+
});
28+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { describe, it, assert } from "vitest";
5+
import { getPlatformInfo, getFrameworkInfo } from "../../../src/util/runtimeInfo-browser.mjs";
6+
7+
describe("runtimeInfo (browser)", function () {
8+
it("getPlatformInfo returns a string containing 'javascript-Browser'", function () {
9+
const info = getPlatformInfo();
10+
assert.include(info, "javascript-Browser");
11+
assert.match(info, /^\(javascript-Browser-.+\)$/);
12+
});
13+
14+
it("getFrameworkInfo returns a string starting with 'Browser/'", function () {
15+
const info = getFrameworkInfo();
16+
assert.match(info, /^Browser\/.+/);
17+
});
18+
});

sdk/core/core-amqp/test/internal/errors.spec.ts

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe("Errors", function () {
3333
];
3434

3535
for (let i = 0; i < cases.length; i++) {
36-
const translatedError = Errors.translate(cases[i].input as any);
36+
const translatedError = Errors.translate(cases[i].input);
3737

3838
assert.equal(translatedError.name, "Error");
3939
assert.equal(
@@ -68,20 +68,22 @@ describe("Errors", function () {
6868
"'Error: getaddrinfo ENOTFOUND example.invalid\n at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:118:26)\n at GetAddrInfoReqWrap.callbackTrampoline (node:internal/async_hooks:130:17)'",
6969
},
7070
};
71-
const translatedError = Errors.translate(testError) as Errors.MessagingError;
71+
const translatedError = Errors.translate(testError);
72+
assert.instanceOf(translatedError, Errors.MessagingError);
7273
assert.equal(testError.error.message, translatedError.message);
7374
assert.equal(translatedError.name, "MessagingError");
7475
assert.equal(translatedError.code, "ENOTFOUND");
7576
assert.equal(translatedError.code, testError.error.code);
7677
assert.equal(translatedError.message, testError.error.message);
7778
assert.equal(translatedError.stack, testError.error.stack);
78-
assert.equal(translatedError.retryable, false);
79+
assert.isFalse(translatedError.retryable);
7980
});
8081

8182
it("Sets retryable to true, if input is custom error and name is OperationTimeoutError", function () {
8283
const err = new Error("error message");
8384
err.name = "OperationTimeoutError";
84-
const translatedError = Errors.translate(err) as Errors.MessagingError;
85+
const translatedError = Errors.translate(err);
86+
assert.instanceOf(translatedError, Errors.MessagingError);
8587
assert.equal(translatedError.name, "MessagingError");
8688
assert.equal(translatedError.code, "OperationTimeoutError");
8789
assert.equal(translatedError.message, err.message);
@@ -92,7 +94,8 @@ describe("Errors", function () {
9294
it("Sets retryable to true, if input is custom error and name is InsufficientCreditError", function () {
9395
const err = new Error("error message");
9496
err.name = "InsufficientCreditError";
95-
const translatedError = Errors.translate(err) as Errors.MessagingError;
97+
const translatedError = Errors.translate(err);
98+
assert.instanceOf(translatedError, Errors.MessagingError);
9699
assert.equal(translatedError.name, "MessagingError");
97100
assert.equal(translatedError.code, "InsufficientCreditError");
98101
assert.equal(translatedError.message, err.message);
@@ -103,7 +106,8 @@ describe("Errors", function () {
103106
it("Does not sets retryable to true, if input is custom error and name is SendOperationFailedError", function () {
104107
const err = new Error("error message");
105108
err.name = "SendOperationFailedError";
106-
const translatedError = Errors.translate(err) as Errors.MessagingError;
109+
const translatedError = Errors.translate(err);
110+
assert.instanceOf(translatedError, Errors.MessagingError);
107111
assert.equal(translatedError.name, "MessagingError");
108112
assert.equal(translatedError.code, "SendOperationFailedError");
109113
assert.equal(translatedError.message, err.message);
@@ -117,7 +121,7 @@ describe("Errors", function () {
117121
assert.equal(translatedError.name, "AbortError");
118122
assert.equal(translatedError.message, err.message);
119123
assert.equal(translatedError.stack, err.stack);
120-
assert.isUndefined((translatedError as Errors.MessagingError).retryable);
124+
assert.notInstanceOf(translatedError, Errors.MessagingError);
121125
});
122126

123127
[
@@ -140,7 +144,8 @@ describe("Errors", function () {
140144
].forEach(function (mapping) {
141145
it("translates " + mapping.from + " into " + mapping.to, function () {
142146
const err: any = new AMQPError(mapping.from, mapping.message);
143-
const translatedError = Errors.translate(err) as Errors.MessagingError;
147+
const translatedError = Errors.translate(err);
148+
assert.instanceOf(translatedError, Errors.MessagingError);
144149
// <unknown> won't have a code since it has no matching condition
145150
if (translatedError.code) {
146151
assert.equal(translatedError.code, mapping.to);
@@ -206,7 +211,8 @@ describe("Errors", function () {
206211
it(
207212
"SystemError from node.js with code: '" + mapping.code + "' to a MessagingError",
208213
function () {
209-
const translatedError = Errors.translate(mapping as any) as Errors.MessagingError;
214+
const translatedError = Errors.translate(mapping);
215+
assert.instanceOf(translatedError, Errors.MessagingError);
210216
assert.equal(translatedError.name, "MessagingError");
211217
assert.equal(translatedError.code, mapping.code);
212218
if (
@@ -350,3 +356,45 @@ describe("Errors", function () {
350356
});
351357
});
352358
});
359+
360+
describe("errors.ts", () => {
361+
it("translate maps AMQP error with status-code: 404 in description to MessagingEntityNotFoundError", () => {
362+
const err: any = {
363+
name: "AmqpProtocolError",
364+
condition: "amqp:not-found",
365+
description: "The messaging entity blah could not be found. status-code: 404",
366+
};
367+
const translated = Errors.translate(err);
368+
assert.instanceOf(translated, Errors.MessagingError);
369+
assert.equal(translated.code, "MessagingEntityNotFoundError");
370+
});
371+
372+
it("translate maps AMQP error with 'messaging entity could not be found' to MessagingEntityNotFoundError", () => {
373+
const err: any = {
374+
name: "AmqpProtocolError",
375+
condition: "amqp:not-found",
376+
description: "The messaging entity 'myentity' could not be found.",
377+
};
378+
const translated = Errors.translate(err);
379+
assert.instanceOf(translated, Errors.MessagingError);
380+
assert.equal(translated.code, "MessagingEntityNotFoundError");
381+
});
382+
383+
it("translate handles already-translated MessagingError", () => {
384+
const err = new Errors.MessagingError("already translated");
385+
const translated = Errors.translate(err);
386+
assert.strictEqual(translated, err);
387+
});
388+
389+
it("translate handles MessageWaitTimeout condition", () => {
390+
const err: any = {
391+
name: "AmqpProtocolError",
392+
condition: "com.microsoft:message-wait-timeout",
393+
description: "No messages available",
394+
};
395+
const translated = Errors.translate(err);
396+
assert.instanceOf(translated, Errors.MessagingError);
397+
assert.equal(translated.name, "MessagingError");
398+
assert.equal(translated.code, "MessageWaitTimeout");
399+
});
400+
});

0 commit comments

Comments
 (0)