Add tests
This commit is contained in:
parent
ba73b0a1f7
commit
428852d619
|
@ -25,7 +25,7 @@ export const NON_RECONNECT_CODES = [
|
|||
|
||||
export enum ManagedSocketErrorType {
|
||||
INVALID_MESSAGE_SHAPE = "Invalid message shape.",
|
||||
CATOSTROPHIC_CLOSE = "Catostrophic close code",
|
||||
CATASTROPHIC_CLOSE = "Catastrophic close code",
|
||||
SOCKET_ERROR = "WebSocket error",
|
||||
CONNECTION_REJECTED = "Connection rejected",
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ type SocketDataHandler = (data: unknown) => void | Promise<void>;
|
|||
type SocketErrorHandler = (error: ManagedSocketError) => void;
|
||||
type SocketOpenHandler = () => void;
|
||||
type SocketCloseHandler = () => void;
|
||||
type SocketCatostrophicErrorHandler = (error: ManagedSocketError) => void;
|
||||
type SocketCatastrophicErrorHandler = (error: ManagedSocketError) => void;
|
||||
|
||||
export type ManagedSocketConnectionState =
|
||||
| "connecting"
|
||||
|
@ -29,7 +29,7 @@ export class ManagedSocket {
|
|||
private onErrorHandler: SocketErrorHandler | null = null;
|
||||
private onOpenHandler: SocketOpenHandler | null = null;
|
||||
private onCloseHandler: SocketCloseHandler | null = null;
|
||||
private onCatostrophicErrorHandler: SocketCatostrophicErrorHandler | null =
|
||||
private onCatastrophicErrorHandler: SocketCatastrophicErrorHandler | null =
|
||||
null;
|
||||
|
||||
private reconnectAttempts: number;
|
||||
|
@ -100,9 +100,9 @@ export class ManagedSocket {
|
|||
this.debugMessage(code);
|
||||
this.onCloseHandler?.();
|
||||
|
||||
// If the code received was catostrophic, terminate the connection
|
||||
if (SocketSpeak.isCatatrophicCloseCode(code)) {
|
||||
this.handleCatostophicError(code);
|
||||
// If the code received was catastrophic, terminate the connection
|
||||
if (SocketSpeak.isCatastrophicCloseCode(code)) {
|
||||
this.handleCatastrophicError(code);
|
||||
}
|
||||
|
||||
this.state = "closed";
|
||||
|
@ -158,7 +158,7 @@ export class ManagedSocket {
|
|||
return this.ws;
|
||||
}
|
||||
|
||||
handleCatostophicError(code: SocketDisconnectCode) {
|
||||
handleCatastrophicError(code: SocketDisconnectCode) {
|
||||
this.state = "terminated";
|
||||
this.attemptReconnect = false;
|
||||
this.debugMessage(
|
||||
|
@ -168,11 +168,11 @@ export class ManagedSocket {
|
|||
const socketError = new ManagedSocketError(
|
||||
"Socket connection terminated due to non-reconnect close code",
|
||||
new Error(code.toString()),
|
||||
ManagedSocketErrorType.CATOSTROPHIC_CLOSE,
|
||||
ManagedSocketErrorType.CATASTROPHIC_CLOSE,
|
||||
);
|
||||
|
||||
if (this.onCatostrophicErrorHandler !== null) {
|
||||
return this.onCatostrophicErrorHandler(socketError);
|
||||
if (this.onCatastrophicErrorHandler !== null) {
|
||||
return this.onCatastrophicErrorHandler(socketError);
|
||||
}
|
||||
|
||||
throw socketError;
|
||||
|
|
|
@ -84,7 +84,7 @@ function prepare<T>(data: T): SerializedSocketMessage {
|
|||
* @param code - The close code to check.
|
||||
* @returns `true` if the code is in the list of non-reconnect codes, otherwise `false`.
|
||||
*/
|
||||
function isCatatrophicCloseCode(code: number) {
|
||||
function isCatastrophicCloseCode(code: number) {
|
||||
return (NON_RECONNECT_CODES as readonly number[]).includes(code);
|
||||
}
|
||||
|
||||
|
@ -93,6 +93,6 @@ export const SocketSpeak = {
|
|||
createHeartbeatMessage,
|
||||
serialize,
|
||||
deserialize,
|
||||
isCatatrophicCloseCode,
|
||||
isCatastrophicCloseCode,
|
||||
prepare,
|
||||
};
|
||||
|
|
76
src/test/messages.test.ts
Normal file
76
src/test/messages.test.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { test, describe, expect } from "bun:test";
|
||||
|
||||
import { SocketSpeak } from "../messages";
|
||||
import { ManagedSocketError } from "../errors";
|
||||
import { NON_RECONNECT_CODES, SocketDisconnectCode } from "../constants";
|
||||
|
||||
describe("createDataMessage()", () => {
|
||||
test("wraps the provided data in a message object", () => {
|
||||
const data = { foo: "bar" };
|
||||
const message = SocketSpeak.createDataMessage(data);
|
||||
expect(message).toEqual({ type: "message", data });
|
||||
});
|
||||
});
|
||||
|
||||
describe("createHeartbeatMessage()", () => {
|
||||
test("creates a heartbeat message with the current timestamp", () => {
|
||||
const message = SocketSpeak.createHeartbeatMessage();
|
||||
expect(message.type).toBe("heartbeat");
|
||||
expect(message.data).toBeTypeOf("string");
|
||||
});
|
||||
});
|
||||
|
||||
describe("serialize()", () => {
|
||||
test("serializes a SocketMessage object into a JSON string", () => {
|
||||
const message = SocketSpeak.createDataMessage({ foo: "bar" });
|
||||
const serialized = SocketSpeak.serialize(message) as string;
|
||||
expect(serialized).toBe(JSON.stringify(message));
|
||||
});
|
||||
});
|
||||
|
||||
describe("deserialize()", () => {
|
||||
test("deserializes a JSON string into a SocketMessage object", () => {
|
||||
const message = SocketSpeak.createDataMessage({ foo: "bar" });
|
||||
const serialized = SocketSpeak.serialize(message);
|
||||
const deserialized = SocketSpeak.deserialize(serialized);
|
||||
expect(deserialized).toEqual(message);
|
||||
});
|
||||
|
||||
test("throws an error if the JSON string cannot be parsed", () => {
|
||||
const invalidData = "invalid";
|
||||
expect(() => SocketSpeak.deserialize(invalidData)).toThrow(
|
||||
ManagedSocketError,
|
||||
);
|
||||
});
|
||||
|
||||
test("throws an error if the message shape is invalid", () => {
|
||||
const invalidData = JSON.stringify({ type: "invalid" });
|
||||
expect(() => SocketSpeak.deserialize(invalidData)).toThrow(
|
||||
ManagedSocketError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("prepare()", () => {
|
||||
test("serializes and wraps the provided data in a message object", () => {
|
||||
const data = { foo: "bar" };
|
||||
const message = SocketSpeak.prepare(data);
|
||||
expect(message).toEqual(SocketSpeak.serialize({ type: "message", data }));
|
||||
});
|
||||
});
|
||||
|
||||
describe("isCatatrophicCloseCode()", () => {
|
||||
test("returns for codes in the catastrophic list", () => {
|
||||
for (const code of NON_RECONNECT_CODES) {
|
||||
expect(SocketSpeak.isCatastrophicCloseCode(code)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("returns false for other close codes", () => {
|
||||
expect(
|
||||
SocketSpeak.isCatastrophicCloseCode(
|
||||
SocketDisconnectCode.CLOSE_SERVICE_RESTART,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
72
src/test/types.test.ts
Normal file
72
src/test/types.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { test, describe, expect } from "bun:test";
|
||||
|
||||
import { parseAnyScoketMessage, parseMessageType } from "../types";
|
||||
import { ManagedSocketError } from "../errors";
|
||||
|
||||
describe("parseMessageType()", () => {
|
||||
test("errors if the value is not a string", () => {
|
||||
expect(() => parseMessageType(123)).toThrowError(ManagedSocketError);
|
||||
});
|
||||
|
||||
test("errors if the value is a string but not a valid message type", () => {
|
||||
expect(() => parseMessageType("invalid")).toThrowError(ManagedSocketError);
|
||||
});
|
||||
|
||||
test("returns the message type when the value is a valid message type", () => {
|
||||
expect(parseMessageType("heartbeat")).toBe("heartbeat");
|
||||
expect(parseMessageType("message")).toBe("message");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseAnyScoketMessage()", () => {
|
||||
test("throws when the value is null", () => {
|
||||
expect(() => parseAnyScoketMessage(null)).toThrowError(ManagedSocketError);
|
||||
});
|
||||
|
||||
test("throws when the value is not an object", () => {
|
||||
expect(() => parseAnyScoketMessage("string")).toThrowError(
|
||||
ManagedSocketError,
|
||||
);
|
||||
expect(() => parseAnyScoketMessage(() => {})).toThrowError(
|
||||
ManagedSocketError,
|
||||
);
|
||||
expect(() => parseAnyScoketMessage(false)).toThrowError(ManagedSocketError);
|
||||
expect(() => parseAnyScoketMessage([])).toThrowError(ManagedSocketError);
|
||||
expect(() => parseAnyScoketMessage(Symbol())).toThrowError(
|
||||
ManagedSocketError,
|
||||
);
|
||||
});
|
||||
|
||||
test("throws when the object does not have the correct shape", () => {
|
||||
expect(() => parseAnyScoketMessage({})).toThrowError(ManagedSocketError);
|
||||
expect(() => parseAnyScoketMessage({ type: "message" })).toThrowError(
|
||||
ManagedSocketError,
|
||||
);
|
||||
expect(() => parseAnyScoketMessage({ data: "data" })).toThrowError(
|
||||
ManagedSocketError,
|
||||
);
|
||||
});
|
||||
|
||||
test("throws when the message type is invalid", () => {
|
||||
expect(() =>
|
||||
parseAnyScoketMessage({ type: "invalid", data: "data" }),
|
||||
).toThrowError(ManagedSocketError);
|
||||
});
|
||||
|
||||
test("throws when the type is 'heartbeat' and the value is not a string", () => {
|
||||
expect(() =>
|
||||
parseAnyScoketMessage({ type: "heartbeat", data: 123 }),
|
||||
).toThrowError(ManagedSocketError);
|
||||
});
|
||||
|
||||
test("returns a valid message object when the type is 'heartbeat'", () => {
|
||||
const dateStr = new Date().toISOString();
|
||||
const message = parseAnyScoketMessage({
|
||||
type: "heartbeat",
|
||||
data: dateStr,
|
||||
});
|
||||
const dataMessage = parseAnyScoketMessage({ type: "message", data: 123 });
|
||||
expect(message).toEqual({ type: "heartbeat", data: dateStr });
|
||||
expect(dataMessage).toEqual({ type: "message", data: 123 });
|
||||
});
|
||||
});
|
|
@ -23,5 +23,7 @@
|
|||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/test"],
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue