msw
Version:
393 lines (332 loc) • 11.6 kB
text/typescript
import { CloseEvent } from '@mswjs/interceptors/WebSocket'
import { http } from '#http/http'
import { graphql } from '../../../graphql'
import { ws } from '../../../ws'
import type { WebSocketNetworkFrameEventMap } from './websocket-frame'
import { WebSocketNetworkFrame } from './websocket-frame'
import { createTestWebSocketConnection } from '../../../../test/support/ws-test-utils'
import { InMemoryHandlersController } from '#core/experimental/handlers-controller'
const gql = graphql.link('*')
beforeAll(() => {
vi.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
vi.clearAllMocks()
})
afterAll(() => {
vi.restoreAllMocks()
})
function spyOnWebSocketFrame(frame: WebSocketNetworkFrame) {
const events: Array<
WebSocketNetworkFrameEventMap[keyof WebSocketNetworkFrameEventMap]
> = []
frame.events.on('*', (event) => events.push(event))
return {
events,
}
}
test('filters only websocket type handlers', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const httpHandlers = [http.post('http://localhost/api/user', () => {})]
const graphqlHandlers = [gql.query('GetUser', () => {})]
const webSocketHandlers = [
ws.link('ws://localhost').addEventListener('connection', () => {}),
]
const controller = new InMemoryHandlersController([
...httpHandlers,
...webSocketHandlers,
...graphqlHandlers,
])
expect(frame.getHandlers(controller)).toEqual(webSocketHandlers)
expect(frame.getHandlers(new InMemoryHandlersController([]))).toEqual([])
})
test('resolves a matching connection', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const connectionListener = vi.fn()
const unhandledFrameCallback = vi.fn()
const api = ws.link('ws://localhost/api')
const matches = await frame.resolve(
[api.addEventListener('connection', connectionListener)],
unhandledFrameCallback,
{ quiet: true },
)
expect.soft(matches).toBe(true)
expect
.soft(connectionListener)
.toHaveBeenCalledExactlyOnceWith(expect.objectContaining(connection))
expect.soft(frame.passthrough).not.toHaveBeenCalled()
expect.soft(frame.errorWith).not.toHaveBeenCalled()
expect.soft(unhandledFrameCallback).not.toHaveBeenCalled()
expect.soft(events).toEqual([
expect.objectContaining({
type: 'websocket:connection',
url: connection.client.url,
protocols: connection.info.protocols,
}),
])
})
test('resolves a connection when there are no handlers', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const connectionListener = vi.fn()
const unhandledFrameCallback = vi.fn()
const matches = await frame.resolve([], unhandledFrameCallback, {
quiet: true,
})
expect.soft(matches).toBe(false)
expect.soft(frame.passthrough).toHaveBeenCalledOnce()
expect.soft(connectionListener).not.toHaveBeenCalled()
expect.soft(frame.errorWith).not.toHaveBeenCalled()
expect.soft(unhandledFrameCallback).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
frame,
}),
)
expect.soft(events).toEqual([
expect.objectContaining({
type: 'websocket:connection',
url: connection.client.url,
protocols: connection.info.protocols,
}),
])
})
test('resolves a non-matching connection', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const connectionListener = vi.fn()
const unhandledFrameCallback = vi.fn()
const api = ws.link('ws://example.com/api')
const matches = await frame.resolve(
[api.addEventListener('connection', connectionListener)],
unhandledFrameCallback,
{ quiet: true },
)
expect.soft(matches).toBe(false)
expect.soft(frame.passthrough).toHaveBeenCalledOnce()
expect.soft(connectionListener).not.toHaveBeenCalled()
expect.soft(frame.errorWith).not.toHaveBeenCalled()
expect.soft(unhandledFrameCallback).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
frame,
}),
)
expect.soft(events).toEqual([
expect.objectContaining({
type: 'websocket:connection',
url: connection.client.url,
protocols: connection.info.protocols,
}),
])
})
test('returns null and prints the error on unhandled exception', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const unhandledFrameCallback = vi.fn()
const api = ws.link('ws://localhost/api')
const exception = new Error('Unhandled exceptin')
await expect
.soft(
frame.resolve(
[
api.addEventListener('connection', () => {
throw exception
}),
],
unhandledFrameCallback,
{ quiet: true },
),
)
.rejects.toThrow(exception)
expect.soft(frame.errorWith).not.toHaveBeenCalled()
expect.soft(frame.passthrough).not.toHaveBeenCalled()
expect.soft(unhandledFrameCallback).not.toHaveBeenCalled()
expect.soft(events).toEqual([
expect.objectContaining({
type: 'websocket:connection',
url: connection.client.url,
protocols: connection.info.protocols,
}),
expect.objectContaining({
type: 'unhandledException',
error: exception,
url: connection.client.url,
protocols: connection.info.protocols,
}),
])
expect.soft(console.error).toHaveBeenCalledTimes(2)
expect.soft(console.error).toHaveBeenNthCalledWith(1, exception)
expect
.soft(console.error)
.toHaveBeenNthCalledWith(
2,
'[MSW] Encountered an unhandled exception during the handler lookup for "ws://localhost/api". Please see the original error above.',
)
})
test('does not print an unhandled exception if the "unhandledException" listener is present', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const unhandledExceptionListener = vi.fn()
frame.events.on('unhandledException', unhandledExceptionListener)
const unhandledFrameCallback = vi.fn()
const api = ws.link('ws://localhost/api')
const exception = new Error('Unhandled exception')
await expect
.soft(
frame.resolve(
[
api.addEventListener('connection', () => {
throw exception
}),
],
unhandledFrameCallback,
{ quiet: true },
),
)
.rejects.toThrow(exception)
expect.soft(frame.errorWith).not.toHaveBeenCalled()
expect.soft(frame.passthrough).not.toHaveBeenCalled()
expect.soft(unhandledFrameCallback).not.toHaveBeenCalled()
expect.soft(events).toEqual([
expect.objectContaining({
type: 'websocket:connection',
url: connection.client.url,
protocols: connection.info.protocols,
}),
expect.objectContaining({
type: 'unhandledException',
error: exception,
url: connection.client.url,
protocols: connection.info.protocols,
}),
])
expect.soft(unhandledExceptionListener).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
error: exception,
url: connection.client.url,
protocols: connection.info.protocols,
}),
)
expect.soft(console.error).not.toHaveBeenCalled()
})
test('emits "websocket:error" when the client connection errors', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const api = ws.link('ws://localhost/api')
await frame.resolve([api.addEventListener('connection', () => {})], vi.fn(), {
quiet: true,
})
connection.client.socket.dispatchEvent(new Event('error'))
expect(events).toEqual([
expect.objectContaining({
type: 'websocket:connection',
}),
expect.objectContaining({
type: 'websocket:error',
url: connection.client.url,
protocols: connection.info.protocols,
error: undefined,
}),
])
})
test('exposes the "error" property of the error event on "websocket:error"', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const api = ws.link('ws://localhost/api')
await frame.resolve([api.addEventListener('connection', () => {})], vi.fn(), {
quiet: true,
})
const error = new Error('Connection failed')
connection.client.socket.dispatchEvent(
Object.assign(new Event('error'), { error }),
)
expect(events[1]).toEqual(
expect.objectContaining({
type: 'websocket:error',
error,
}),
)
})
test('exposes the "cause" property of the error event on "websocket:error"', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const api = ws.link('ws://localhost/api')
await frame.resolve([api.addEventListener('connection', () => {})], vi.fn(), {
quiet: true,
})
const cause = new Error('Connection failed')
connection.client.socket.dispatchEvent(
Object.assign(new Event('error'), { cause }),
)
expect(events[1]).toEqual(
expect.objectContaining({
type: 'websocket:error',
error: cause,
}),
)
})
test('stops emitting "websocket:error" once the client connection closes', async () => {
class WebSocketFrame extends WebSocketNetworkFrame {
passthrough = vi.fn()
errorWith = vi.fn()
}
const connection = createTestWebSocketConnection('ws://localhost/api')
const frame = new WebSocketFrame({ connection })
const { events } = spyOnWebSocketFrame(frame)
const api = ws.link('ws://localhost/api')
await frame.resolve([api.addEventListener('connection', () => {})], vi.fn(), {
quiet: true,
})
connection.client.socket.dispatchEvent(new CloseEvent('close'))
connection.client.socket.dispatchEvent(new Event('error'))
expect(events).toEqual([
expect.objectContaining({
type: 'websocket:connection',
}),
])
})