Add tests
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user