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,261 @@
import {ConnectionAuth, AuthStatus, AuthMode} from "../src/ConnectionAuth"
import {Connection} from "../src/Connection"
import {ConnectionEvent} from "../src/ConnectionEvent"
import {ctx, sleep} from "@welshman/lib"
import {vi, describe, it, expect, beforeEach, afterEach} from "vitest"
import {SocketStatus} from "../src/Socket"
describe("ConnectionAuth", () => {
let connection: Connection
let auth: ConnectionAuth
let mockSignEvent: any
beforeEach(() => {
vi.useFakeTimers()
connection = new Connection("wss://test.relay/")
// Mock socket operations
connection.socket.open = vi.fn().mockResolvedValue(undefined)
connection.socket.status = SocketStatus.Open
connection.send = vi.fn().mockResolvedValue(undefined)
auth = connection.auth
mockSignEvent = vi.fn()
ctx.net = {...ctx.net, signEvent: mockSignEvent, authMode: AuthMode.Explicit}
})
afterEach(() => {
vi.useRealTimers()
})
describe("initialization", () => {
it("should initialize with None status", () => {
expect(auth.status).toBe(AuthStatus.None)
expect(auth.challenge).toBeUndefined()
expect(auth.request).toBeUndefined()
expect(auth.message).toBeUndefined()
})
})
describe("message handling", () => {
it("should handle AUTH message and set challenge", () => {
connection.emit(ConnectionEvent.Receive, ["AUTH", "challenge123"])
expect(auth.challenge).toBe("challenge123")
expect(auth.status).toBe(AuthStatus.Requested)
})
it("should ignore AUTH message if challenge matches current challenge", () => {
auth.challenge = "challenge123"
auth.status = AuthStatus.PendingResponse
connection.emit(ConnectionEvent.Receive, ["AUTH", "challenge123"])
expect(auth.status).toBe(AuthStatus.PendingResponse)
})
it("should handle successful OK message", () => {
auth.challenge = "challenge123"
auth.request = "request123"
auth.status = AuthStatus.PendingResponse
connection.emit(ConnectionEvent.Receive, ["OK", "request123", true, "success"])
expect(auth.status).toBe(AuthStatus.Ok)
expect(auth.message).toBe("success")
})
it("should handle failed OK message", () => {
auth.challenge = "challenge123"
auth.request = "request123"
auth.status = AuthStatus.PendingResponse
connection.emit(ConnectionEvent.Receive, ["OK", "request123", false, "forbidden"])
expect(auth.status).toBe(AuthStatus.Forbidden)
expect(auth.message).toBe("forbidden")
})
it("should ignore OK message for different request", () => {
auth.challenge = "challenge123"
auth.request = "request123"
auth.status = AuthStatus.PendingResponse
connection.emit(ConnectionEvent.Receive, ["OK", "different123", true, "success"])
expect(auth.status).toBe(AuthStatus.PendingResponse)
expect(auth.message).toBeUndefined()
})
})
describe("connection close handling", () => {
it("should reset state on connection close", () => {
auth.challenge = "challenge123"
auth.request = "request123"
auth.message = "message"
auth.status = AuthStatus.Ok
connection.emit(ConnectionEvent.Close)
expect(auth.challenge).toBeUndefined()
expect(auth.request).toBeUndefined()
expect(auth.message).toBeUndefined()
expect(auth.status).toBe(AuthStatus.None)
})
})
describe("respond()", () => {
it("should throw if no challenge exists", async () => {
await expect(auth.respond()).rejects.toThrow("Attempted to authenticate with no challenge")
})
it("should throw if status is not Requested", async () => {
auth.challenge = "challenge123"
auth.status = AuthStatus.Ok
await expect(auth.respond()).rejects.toThrow(
"Attempted to authenticate when auth is already ok",
)
})
it("should handle successful signature", async () => {
auth.challenge = "challenge123"
auth.status = AuthStatus.Requested
const signedEvent = {id: "event123" /* other event fields */}
mockSignEvent.mockResolvedValue(signedEvent)
await auth.respond()
expect(auth.request).toBe("event123")
expect(auth.status).toBe(AuthStatus.PendingResponse)
expect(connection.send).toHaveBeenCalledWith(["AUTH", signedEvent])
})
it("should handle denied signature", async () => {
auth.challenge = "challenge123"
auth.status = AuthStatus.Requested
mockSignEvent.mockResolvedValue(undefined)
await auth.respond()
expect(auth.status).toBe(AuthStatus.DeniedSignature)
expect(connection.send).not.toHaveBeenCalled()
})
})
describe("automatic authentication", () => {
it("should auto-respond in implicit mode", () => {
ctx.net.authMode = AuthMode.Implicit
const respondSpy = vi.spyOn(auth, "respond")
connection.emit(ConnectionEvent.Receive, ["AUTH", "challenge123"])
expect(respondSpy).toHaveBeenCalled()
})
it("should not auto-respond in explicit mode", () => {
ctx.net.authMode = AuthMode.Explicit
const respondSpy = vi.spyOn(auth, "respond")
connection.emit(ConnectionEvent.Receive, ["AUTH", "challenge123"])
expect(respondSpy).not.toHaveBeenCalled()
})
})
describe("waitFor methods", () => {
it("should wait for challenge", async () => {
const waitPromise = auth.waitForChallenge()
setTimeout(() => {
connection.emit(ConnectionEvent.Receive, ["AUTH", "challenge123"])
}, 100)
vi.advanceTimersByTime(100)
await waitPromise
expect(auth.challenge).toBe("challenge123")
})
it("should timeout waiting for challenge", async () => {
const waitPromise = auth.waitForChallenge(50)
vi.advanceTimersByTime(100)
await waitPromise
expect(auth.challenge).toBeUndefined()
})
it("should wait for resolution", async () => {
auth.challenge = "challenge123"
auth.request = "request123"
auth.status = AuthStatus.PendingResponse
const waitPromise = auth.waitForResolution()
setTimeout(() => {
connection.emit(ConnectionEvent.Receive, ["OK", "request123", true, "success"])
}, 100)
vi.advanceTimersByTime(100)
await waitPromise
expect(auth.status).toBe(AuthStatus.Ok)
})
it("should timeout waiting for resolution", async () => {
auth.status = AuthStatus.PendingResponse
const waitPromise = auth.waitForResolution(50)
vi.advanceTimersByTime(100)
await waitPromise
expect(auth.status).toBe(AuthStatus.PendingResponse)
})
})
describe("attempt()", () => {
it("should complete full authentication flow", async () => {
const signedEvent = {id: "event123" /* other event fields */}
mockSignEvent.mockResolvedValue(signedEvent)
const attemptPromise = auth.attempt()
// Simulate socket opening and challenge received
setTimeout(() => {
connection.emit(ConnectionEvent.Receive, ["AUTH", "challenge123"])
}, 100)
await vi.advanceTimersByTimeAsync(100)
// Simulate successful authentication
setTimeout(() => {
connection.emit(ConnectionEvent.Receive, ["OK", "event123", true, "success"])
}, 200)
await vi.advanceTimersByTimeAsync(200)
await attemptPromise
expect(auth.status).toBe(AuthStatus.Ok)
})
it("should handle authentication failure", async () => {
mockSignEvent.mockResolvedValue(undefined)
const attemptPromise = auth.attempt()
setTimeout(() => {
connection.emit(ConnectionEvent.Receive, ["AUTH", "challenge123"])
}, 100)
await vi.advanceTimersByTimeAsync(200)
await attemptPromise
expect(auth.status).toBe(AuthStatus.DeniedSignature)
})
it("should timeout if no challenge received", async () => {
const attemptPromise = auth.attempt(100)
// 2 loops (2 * 100ms) in the waitForChallenge before timeout
// 1 loop in waitForResolution as it reach the condition immediately
await vi.advanceTimersByTimeAsync(100)
await attemptPromise
expect(auth.status).toBe(AuthStatus.None)
})
})
})