Files
aiturk-hermes-ide/apps/desktop/src/app/settings/billing/use-step-up.test.tsx
T

133 lines
3.5 KiB
TypeScript

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook, waitFor } from '@testing-library/react'
import { createElement, type PropsWithChildren } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const apiMocks = vi.hoisted(() => ({
stepUp: vi.fn()
}))
const gatewayMock = vi.hoisted(() => {
const handlers = new Map<string, Set<(event: unknown) => void>>()
const gateway = {
on: vi.fn((eventName: string, handler: (event: unknown) => void) => {
let set = handlers.get(eventName)
if (!set) {
set = new Set()
handlers.set(eventName, set)
}
set.add(handler)
return () => set?.delete(handler)
})
}
return {
count: (eventName: string) => handlers.get(eventName)?.size ?? 0,
emit: (eventName: string, event: unknown) => {
handlers.get(eventName)?.forEach(handler => handler(event))
},
gateway,
reset: () => {
handlers.clear()
gateway.on.mockClear()
}
}
})
vi.mock('@/store/gateway', async () => {
const { atom } = (await vi.importActual('nanostores')) as { atom: (value: unknown) => unknown }
return {
$gateway: atom(gatewayMock.gateway)
}
})
vi.mock('./api', () => ({
useBillingApi: () => ({
stepUp: apiMocks.stepUp
})
}))
import { useStepUpFlow } from './use-step-up'
function createWrapper(client: QueryClient) {
return function wrapper({ children }: PropsWithChildren) {
return createElement(QueryClientProvider, { client }, children)
}
}
beforeEach(() => {
apiMocks.stepUp.mockReset()
gatewayMock.reset()
})
afterEach(() => {
vi.clearAllMocks()
})
describe('useStepUpFlow', () => {
it('subscribes for verification, opens the verification URL, cleans up, and invalidates on completion', async () => {
let resolveStepUp: (value: unknown) => void = () => {}
const stepUpPromise = new Promise(resolve => {
resolveStepUp = resolve
})
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
const invalidate = vi.spyOn(client, 'invalidateQueries')
apiMocks.stepUp.mockReturnValue(stepUpPromise)
Object.defineProperty(window, 'hermesDesktop', {
configurable: true,
value: {
openExternal: vi.fn()
}
})
const { result, unmount } = renderHook(() => useStepUpFlow(), { wrapper: createWrapper(client) })
act(() => {
void result.current.start()
})
expect(result.current.phase).toBe('waiting')
expect(gatewayMock.count('billing.step_up.verification')).toBe(1)
act(() => {
gatewayMock.emit('billing.step_up.verification', {
payload: {
user_code: 'ABCD-1234',
verification_url: 'https://portal.nousresearch.com/device'
},
type: 'billing.step_up.verification'
})
})
expect(result.current.phase).toBe('verifying')
expect(result.current.verification).toEqual({
code: 'ABCD-1234',
url: 'https://portal.nousresearch.com/device'
})
result.current.openVerification()
expect(window.hermesDesktop?.openExternal).toHaveBeenCalledWith('https://portal.nousresearch.com/device')
await act(async () => {
resolveStepUp({ data: { granted: true, ok: true }, ok: true })
await stepUpPromise
})
await waitFor(() => {
expect(invalidate).toHaveBeenCalledWith({ queryKey: ['billing', 'state'] })
expect(invalidate).toHaveBeenCalledWith({ queryKey: ['billing', 'subscription'] })
})
unmount()
expect(gatewayMock.count('billing.step_up.verification')).toBe(0)
})
})