201 lines
5.9 KiB
TypeScript
201 lines
5.9 KiB
TypeScript
import {ConnectionSender} from "../src/ConnectionSender"
|
|
import {Connection} from "../src/Connection"
|
|
import {Message, SocketStatus} from "../src/Socket"
|
|
import {AuthStatus} from "../src/ConnectionAuth"
|
|
import {AUTH_JOIN} from "@welshman/util"
|
|
import {vi, describe, it, expect, beforeEach, afterEach} from "vitest"
|
|
|
|
describe("ConnectionSender", () => {
|
|
let connection: Connection
|
|
let sender: ConnectionSender
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
connection = new Connection("wss://test.relay/")
|
|
connection.socket.send = vi.fn().mockResolvedValue(undefined)
|
|
connection.socket.open = vi.fn().mockResolvedValue(undefined)
|
|
connection.socket.status = SocketStatus.Open
|
|
connection.send = vi.fn().mockResolvedValue(undefined)
|
|
|
|
sender = connection.sender
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
describe("message deferral", () => {
|
|
it("should not defer CLOSE messages", async () => {
|
|
// First send a REQ message to set up the pending request
|
|
const reqId = "subscription-id"
|
|
sender.push([
|
|
"REQ",
|
|
reqId,
|
|
{
|
|
/* filters */
|
|
},
|
|
] as Message)
|
|
const message: Message = ["CLOSE", reqId]
|
|
// there is a setTimeout in the worker, so we need to advance timers
|
|
vi.advanceTimersByTime(50)
|
|
sender.push(message)
|
|
// there is a setTimeout in the worker, so we need to advance timers
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).toHaveBeenCalledWith(message)
|
|
})
|
|
|
|
it("should defer messages when socket is not open", () => {
|
|
connection.socket.status = SocketStatus.Closed
|
|
const message: Message = [
|
|
"EVENT",
|
|
{
|
|
/* event data */
|
|
},
|
|
]
|
|
sender.push(message)
|
|
expect(connection.socket.send).not.toHaveBeenCalled()
|
|
expect(sender.worker.buffer).toContain(message)
|
|
})
|
|
|
|
it("should not defer AUTH messages", () => {
|
|
const message: Message = [
|
|
"AUTH",
|
|
{
|
|
/* auth data */
|
|
},
|
|
]
|
|
sender.push(message)
|
|
// there is a setTimeout in the worker, so we need to advance timers
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).toHaveBeenCalledWith(message)
|
|
})
|
|
|
|
it("should not defer AUTH_JOIN event messages", () => {
|
|
const message: Message = ["EVENT", {kind: AUTH_JOIN}]
|
|
sender.push(message)
|
|
// there is a setTimeout in the worker, so we need to advance timers
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).toHaveBeenCalledWith(message)
|
|
})
|
|
|
|
it("should defer messages when auth is pending", () => {
|
|
connection.socket.status = SocketStatus.Open
|
|
connection.auth.status = AuthStatus.PendingResponse
|
|
const message: Message = [
|
|
"EVENT",
|
|
{
|
|
/* event data */
|
|
},
|
|
]
|
|
sender.push(message)
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).not.toHaveBeenCalled()
|
|
expect(sender.worker.buffer).toContain(message)
|
|
})
|
|
|
|
it("should defer REQ messages when too many pending requests", () => {
|
|
connection.socket.status = SocketStatus.Open
|
|
connection.auth.status = AuthStatus.Ok
|
|
// Set up 8 pending requests
|
|
for (let i = 0; i < 8; i++) {
|
|
connection.state.pendingRequests.set(`req${i}`, {
|
|
filters: [],
|
|
sent: Date.now(),
|
|
})
|
|
}
|
|
|
|
const message: Message = [
|
|
"REQ",
|
|
"newReq",
|
|
{
|
|
/* filter */
|
|
},
|
|
]
|
|
sender.push(message)
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).not.toHaveBeenCalled()
|
|
expect(sender.worker.buffer).toContain(message)
|
|
})
|
|
})
|
|
|
|
describe("message handling", () => {
|
|
it("should send messages when conditions are met", () => {
|
|
connection.socket.status = SocketStatus.Open
|
|
connection.auth.status = AuthStatus.Ok
|
|
const message: Message = [
|
|
"EVENT",
|
|
{
|
|
/* event data */
|
|
},
|
|
]
|
|
sender.push(message)
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).toHaveBeenCalledWith(message)
|
|
})
|
|
|
|
it("should handle CLOSE messages for non-existent requests", () => {
|
|
const message: Message = ["CLOSE", "non-existent-req"]
|
|
sender.push(message)
|
|
expect(connection.socket.send).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it("should remove pending REQ when handling CLOSE", () => {
|
|
const reqId = "req123"
|
|
const reqMessage: Message = [
|
|
"REQ",
|
|
reqId,
|
|
{
|
|
/* filter */
|
|
},
|
|
]
|
|
sender.worker.buffer.push(reqMessage)
|
|
|
|
const closeMessage: Message = ["CLOSE", reqId]
|
|
sender.push(closeMessage)
|
|
|
|
expect(sender.worker.buffer).not.toContain(reqMessage)
|
|
})
|
|
})
|
|
|
|
describe("worker behavior", () => {
|
|
it("should process deferred messages when conditions become favorable", async () => {
|
|
connection.socket.status = SocketStatus.Closed
|
|
const message: Message = [
|
|
"EVENT",
|
|
{
|
|
/* event data */
|
|
},
|
|
]
|
|
sender.push(message)
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).not.toHaveBeenCalled()
|
|
|
|
// Simulate socket opening and auth completing
|
|
connection.socket.status = SocketStatus.Open
|
|
connection.auth.status = AuthStatus.Ok
|
|
|
|
// Trigger worker processing
|
|
sender.worker.resume()
|
|
vi.advanceTimersByTime(50)
|
|
expect(connection.socket.send).toHaveBeenCalledWith(message)
|
|
})
|
|
|
|
it("should maintain message order", async () => {
|
|
connection.socket.status = SocketStatus.Open
|
|
connection.auth.status = AuthStatus.Ok
|
|
|
|
const messages: Message[] = [
|
|
["EVENT", {id: "1"}],
|
|
["EVENT", {id: "2"}],
|
|
["EVENT", {id: "3"}],
|
|
]
|
|
|
|
messages.forEach(msg => sender.push(msg))
|
|
vi.advanceTimersByTime(50)
|
|
|
|
const sendCalls = connection.socket.send.mock.calls
|
|
expect(sendCalls.map(call => call[0])).toEqual(messages)
|
|
})
|
|
})
|
|
})
|