Add tests

This commit is contained in:
Ticruz
2025-02-04 13:21:23 +01:00
committed by Jon Staab
parent 917727c86f
commit 8a2b62f693
57 changed files with 9231 additions and 25 deletions
@@ -0,0 +1,258 @@
import {ctx} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {vi, describe, it, expect, beforeEach} from "vitest"
import {Subscription, SubscriptionEvent} from "../../src/Subscribe"
import {ConnectionEvent} from "../../src/ConnectionEvent"
describe("Subscription", () => {
let mockExecutor: any
let mockConnection: any
let mockExecutorSub: any
const relayUrl = "wss://test.relay/"
beforeEach(() => {
vi.useFakeTimers()
mockExecutorSub = {unsubscribe: vi.fn()}
mockConnection = {
url: relayUrl,
auth: {attempt: vi.fn().mockResolvedValue(undefined)},
on: vi.fn(),
off: vi.fn(),
}
mockExecutor = {
subscribe: vi.fn().mockReturnValue(mockExecutorSub),
target: {
connections: [mockConnection],
cleanup: vi.fn(),
},
}
ctx.net = {
...ctx.net,
getExecutor: vi.fn().mockReturnValue(mockExecutor),
isDeleted: vi.fn().mockReturnValue(false),
matchFilters: vi.fn().mockReturnValue(true),
isValid: vi.fn().mockReturnValue(true),
}
})
describe("event handling", () => {
it("should handle duplicate events", () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [],
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Duplicate, spy)
// Simulate duplicate event
const event = {id: "event123"} as TrustedEvent
sub.tracker.track(event.id, relayUrl)
sub.onEvent(relayUrl, event)
expect(spy).toHaveBeenCalledWith(relayUrl, event)
})
it("should handle deleted events", () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [],
})
const spy = vi.fn()
sub.on(SubscriptionEvent.DeletedEvent, spy)
// @ts-ignore
ctx.net.isDeleted.mockReturnValue(true)
const event = {id: "event123"} as TrustedEvent
sub.onEvent(relayUrl, event)
expect(spy).toHaveBeenCalledWith(relayUrl, event)
})
it("should handle failed filters", () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [],
})
const spy = vi.fn()
sub.on(SubscriptionEvent.FailedFilter, spy)
// @ts-ignore
ctx.net.matchFilters.mockReturnValue(false)
const event = {id: "event123"} as TrustedEvent
sub.onEvent(relayUrl, event)
expect(spy).toHaveBeenCalledWith(relayUrl, event)
})
it("should handle invalid events", () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [],
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Invalid, spy)
// @ts-ignore
ctx.net.isValid.mockReturnValue(false)
const event = {id: "event123"} as TrustedEvent
sub.onEvent(relayUrl, event)
expect(spy).toHaveBeenCalledWith(relayUrl, event)
})
it("should handle valid events", () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [],
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Event, spy)
const event = {id: "event123"} as TrustedEvent
sub.onEvent(relayUrl, event)
expect(spy).toHaveBeenCalledWith(relayUrl, event)
})
})
describe("execution", () => {
it("should setup auth timeout", async () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
authTimeout: 1000,
})
await sub.execute()
expect(mockConnection.auth.attempt).toHaveBeenCalledWith(1000)
})
it("should chunk filters", async () => {
const filters = Array(10).fill({kinds: [1]})
const sub = new Subscription({
relays: [relayUrl],
filters,
})
await sub.execute()
expect(mockExecutor.subscribe).toHaveBeenCalledTimes(2) // 8 filters + 2 filters
})
it("should handle empty filters", async () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [],
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Complete, spy)
await sub.execute()
expect(spy).toHaveBeenCalled()
expect(mockExecutor.subscribe).not.toHaveBeenCalled()
})
it("should setup connection close handlers", async () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
})
await sub.execute()
expect(mockConnection.on).toHaveBeenCalledWith(ConnectionEvent.Close, sub.onClose)
})
})
describe("completion", () => {
it("should complete on timeout", async () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
timeout: 1000,
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Complete, spy)
await sub.execute()
await vi.advanceTimersByTimeAsync(1000)
expect(spy).toHaveBeenCalled()
})
it("should complete on abort signal", async () => {
const controller = new AbortController()
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
signal: controller.signal,
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Complete, spy)
await sub.execute()
controller.abort()
expect(spy).toHaveBeenCalled()
})
it("should complete when all relays close", () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Complete, spy)
sub.onClose(mockConnection)
expect(spy).toHaveBeenCalled()
})
it("should complete on EOSE when closeOnEose is true", () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
closeOnEose: true,
})
const spy = vi.fn()
sub.on(SubscriptionEvent.Complete, spy)
sub.onEose(relayUrl)
expect(spy).toHaveBeenCalled()
})
})
describe("cleanup", () => {
it("should cleanup on completion", async () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
})
await sub.execute()
sub.onComplete()
expect(mockExecutorSub.unsubscribe).toHaveBeenCalled()
expect(mockExecutor.target.cleanup).toHaveBeenCalled()
expect(mockConnection.off).toHaveBeenCalledWith(ConnectionEvent.Close, sub.onClose)
})
it("should only cleanup once", async () => {
const sub = new Subscription({
relays: [relayUrl],
filters: [{kinds: [1]}],
})
await sub.execute()
sub.onComplete()
sub.onComplete()
expect(mockExecutorSub.unsubscribe).toHaveBeenCalledTimes(1)
expect(mockExecutor.target.cleanup).toHaveBeenCalledTimes(1)
})
})
})
@@ -0,0 +1,173 @@
import {ctx} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {vi, describe, it, expect, beforeEach} from "vitest"
import {
calculateSubscriptionGroup,
mergeSubscriptions,
Subscription,
SubscriptionEvent,
} from "../../src/Subscribe"
describe("Subscription optimization", () => {
let mockExecutor: any
beforeEach(() => {
// Setup mock executor
mockExecutor = {
subscribe: vi.fn().mockReturnValue({unsubscribe: vi.fn()}),
target: {
connections: [],
cleanup: vi.fn(),
},
}
ctx.net = {
...ctx.net,
optimizeSubscriptions: vi.fn(subs =>
subs.map(sub => ({
relays: sub.request.relays,
filters: sub.request.filters,
})),
),
getExecutor: vi.fn().mockReturnValue(mockExecutor),
isDeleted: vi.fn().mockReturnValue(false),
matchFilters: vi.fn().mockReturnValue(true),
isValid: vi.fn().mockReturnValue(true),
}
})
describe("calculateSubscriptionGroup", () => {
it("should group by timeout", () => {
const sub = new Subscription({
relays: ["relay1"],
filters: [],
timeout: 1000,
})
expect(calculateSubscriptionGroup(sub)).toBe("timeout:1000")
})
it("should group by auth timeout", () => {
const sub = new Subscription({
relays: ["relay1"],
filters: [],
authTimeout: 500,
})
expect(calculateSubscriptionGroup(sub)).toBe("authTimeout:500")
})
it("should group by closeOnEose", () => {
const sub = new Subscription({
relays: ["relay1"],
filters: [],
closeOnEose: true,
})
expect(calculateSubscriptionGroup(sub)).toBe("closeOnEose")
})
it("should combine multiple properties", () => {
const sub = new Subscription({
relays: ["relay1"],
filters: [],
timeout: 1000,
authTimeout: 500,
closeOnEose: true,
})
expect(calculateSubscriptionGroup(sub)).toBe("timeout:1000|authTimeout:500|closeOnEose")
})
})
describe("mergeSubscriptions", () => {
it("should merge relays and filters", () => {
const subs = [
new Subscription({
relays: ["relay1"],
filters: [{kinds: [1]}],
}),
new Subscription({
relays: ["relay2"],
filters: [{kinds: [2]}],
}),
]
const merged = mergeSubscriptions(subs)
expect(merged.request.relays).toEqual(["relay1", "relay2"])
expect(merged.request.filters).toEqual([{kinds: [1, 2]}])
})
it("should propagate events from original subscriptions to merged subscription", () => {
const mergedSpy = vi.fn()
const subs = [
new Subscription({
relays: ["relay1"],
filters: [{kinds: [1]}],
}),
new Subscription({
relays: ["relay2"],
filters: [{kinds: [1]}],
}),
]
const merged = mergeSubscriptions(subs)
merged.on(SubscriptionEvent.Event, mergedSpy)
const event = {id: "event123", kind: 1} as TrustedEvent
// Simulate event from original subscription
subs[0].emit(SubscriptionEvent.Event, "relay1", event)
expect(mergedSpy).toHaveBeenCalledWith("relay1", event)
})
it("should avoid duplicate events in merged subscription", () => {
const mergedSpy = vi.fn()
const subs = [
new Subscription({
relays: ["relay1"],
filters: [{kinds: [1]}],
}),
new Subscription({
relays: ["relay2"],
filters: [{kinds: [1]}],
}),
]
const merged = mergeSubscriptions(subs)
merged.on(SubscriptionEvent.Event, mergedSpy)
const event = {id: "event123", kind: 1} as TrustedEvent
// Simulate same event from both subscriptions
subs[0].emit(SubscriptionEvent.Event, "relay1", event)
subs[1].emit(SubscriptionEvent.Event, "relay2", event)
expect(mergedSpy).toHaveBeenCalledTimes(1)
expect(mergedSpy).toHaveBeenCalledWith("relay1", event)
})
it("should complete when all subscriptions complete", () => {
const spy = vi.fn()
const subs = [
new Subscription({
relays: ["relay1"],
filters: [{kinds: [1]}],
}),
new Subscription({
relays: ["relay2"],
filters: [{kinds: [1]}],
}),
]
const merged = mergeSubscriptions(subs)
merged.on(SubscriptionEvent.Complete, spy)
subs[0].emit(SubscriptionEvent.Complete)
expect(spy).not.toHaveBeenCalled()
subs[1].emit(SubscriptionEvent.Complete)
expect(spy).toHaveBeenCalled()
})
})
})