221 lines
6.5 KiB
TypeScript
221 lines
6.5 KiB
TypeScript
// Copyright (C) 2026 Whiterun LLC,
|
|
// This software is licensed under the GNU Lesser General Public License (LGPL), version 3.0 or later.
|
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/lgpl-3.0.html
|
|
|
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import { MessageQueue } from "./message-queue.js";
|
|
import { ProtocolMessage, RelayMsgAction } from "./protocols/hdwalletv1.js";
|
|
|
|
describe("MessageQueue", () => {
|
|
let queue: MessageQueue;
|
|
let publishFn: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
queue = new MessageQueue({ logActivity: false });
|
|
publishFn = vi.fn().mockResolvedValue(undefined);
|
|
});
|
|
|
|
describe("initialization", () => {
|
|
it("should start with ready state false", () => {
|
|
expect(queue.getReady()).toBe(false);
|
|
});
|
|
|
|
it("should start with empty queue", () => {
|
|
expect(queue.getQueueLength()).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("enqueue", () => {
|
|
it("should queue messages when not ready", async () => {
|
|
const message: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
const promise = queue.enqueue(message);
|
|
|
|
expect(queue.getQueueLength()).toBe(1);
|
|
expect(queue.getReady()).toBe(false);
|
|
|
|
// Message should be queued, promise should not resolve yet
|
|
await expect(
|
|
Promise.race([
|
|
promise,
|
|
new Promise((resolve) => setTimeout(() => resolve("timeout"), 10)),
|
|
]),
|
|
).resolves.toBe("timeout");
|
|
});
|
|
|
|
it("should return resolved promise when already ready", async () => {
|
|
queue.setReady(publishFn);
|
|
|
|
const message: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
await expect(queue.enqueue(message)).resolves.toBeUndefined();
|
|
expect(queue.getQueueLength()).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("setReady", () => {
|
|
it("should mark queue as ready", () => {
|
|
queue.setReady(publishFn);
|
|
expect(queue.getReady()).toBe(true);
|
|
});
|
|
|
|
it("should process queued messages when set to ready", async () => {
|
|
const message1: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
const message2: ProtocolMessage = {
|
|
action: RelayMsgAction.WalletReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
const promise1 = queue.enqueue(message1);
|
|
const promise2 = queue.enqueue(message2);
|
|
|
|
expect(queue.getQueueLength()).toBe(2);
|
|
|
|
await queue.setReady(publishFn);
|
|
|
|
expect(publishFn).toHaveBeenCalledTimes(2);
|
|
expect(publishFn).toHaveBeenCalledWith(message1);
|
|
expect(publishFn).toHaveBeenCalledWith(message2);
|
|
expect(queue.getQueueLength()).toBe(0);
|
|
|
|
// Promises should resolve
|
|
await expect(promise1).resolves.toBeUndefined();
|
|
await expect(promise2).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("should reject queued messages if publish fails", async () => {
|
|
const error = new Error("Publish failed");
|
|
publishFn.mockRejectedValueOnce(error);
|
|
|
|
const message: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
const promise = queue.enqueue(message);
|
|
await queue.setReady(publishFn);
|
|
|
|
await expect(promise).rejects.toThrow("Publish failed");
|
|
});
|
|
|
|
it("should process messages in order", async () => {
|
|
const messages: ProtocolMessage[] = [];
|
|
const callOrder: number[] = [];
|
|
|
|
publishFn.mockImplementation(async (msg: ProtocolMessage) => {
|
|
const index = messages.length;
|
|
messages.push(msg);
|
|
callOrder.push(index);
|
|
});
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
const message: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
queue.enqueue(message);
|
|
}
|
|
|
|
await queue.setReady(publishFn);
|
|
|
|
expect(callOrder).toEqual([0, 1, 2, 3, 4]);
|
|
expect(messages).toHaveLength(5);
|
|
});
|
|
});
|
|
|
|
describe("setNotReady", () => {
|
|
it("should mark queue as not ready", () => {
|
|
queue.setReady(publishFn);
|
|
expect(queue.getReady()).toBe(true);
|
|
|
|
queue.setNotReady();
|
|
expect(queue.getReady()).toBe(false);
|
|
});
|
|
|
|
it("should reject all queued messages", async () => {
|
|
const message1: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
const message2: ProtocolMessage = {
|
|
action: RelayMsgAction.WalletReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
const promise1 = queue.enqueue(message1);
|
|
const promise2 = queue.enqueue(message2);
|
|
|
|
queue.setNotReady("Custom error message");
|
|
|
|
await expect(promise1).rejects.toThrow("Custom error message");
|
|
await expect(promise2).rejects.toThrow("Custom error message");
|
|
expect(queue.getQueueLength()).toBe(0);
|
|
});
|
|
|
|
it("should use default error message if not provided", async () => {
|
|
const message: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
const promise = queue.enqueue(message);
|
|
queue.setNotReady();
|
|
|
|
await expect(promise).rejects.toThrow(
|
|
"Connection closed before message could be sent",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("clear", () => {
|
|
it("should clear all queued messages", async () => {
|
|
const message: ProtocolMessage = {
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
const promise = queue.enqueue(message);
|
|
expect(queue.getQueueLength()).toBe(1);
|
|
|
|
queue.clear();
|
|
|
|
expect(queue.getQueueLength()).toBe(0);
|
|
await expect(promise).rejects.toThrow("Queue cleared");
|
|
});
|
|
|
|
it("should not affect ready state", () => {
|
|
queue.setReady(publishFn);
|
|
expect(queue.getReady()).toBe(true);
|
|
|
|
queue.clear();
|
|
expect(queue.getReady()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("getQueueLength", () => {
|
|
it("should return correct queue length", () => {
|
|
expect(queue.getQueueLength()).toBe(0);
|
|
|
|
queue.enqueue({
|
|
action: RelayMsgAction.DappReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
});
|
|
expect(queue.getQueueLength()).toBe(1);
|
|
|
|
queue.enqueue({
|
|
action: RelayMsgAction.WalletReady,
|
|
time: Math.floor(Date.now() / 1000),
|
|
});
|
|
expect(queue.getQueueLength()).toBe(2);
|
|
});
|
|
});
|
|
});
|