cc-haha/desktop/src/stores/hahaOpenAIOAuthStore.test.ts
程序员阿江(Relakkes) 83ee4c09ec Add desktop ChatGPT OAuth controls
The desktop app now has a dedicated OpenAI OAuth API client and store mirroring the existing Claude Official flow. The component uses the existing browser-open plus polling pattern and never exposes token material to the UI.

Constraint: OAuth status responses must not return token bodies

Rejected: Reuse Claude OAuth store | the status shape and endpoint differ

Confidence: high

Scope-risk: narrow

Tested: cd desktop && bun run test -- src/stores/hahaOpenAIOAuthStore.test.ts

Tested: git diff --check
2026-05-18 23:53:11 +08:00

98 lines
2.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const { startMock, statusMock, logoutMock } = vi.hoisted(() => ({
startMock: vi.fn(),
statusMock: vi.fn(),
logoutMock: vi.fn(),
}))
vi.mock('../api/hahaOpenAIOAuth', () => ({
hahaOpenAIOAuthApi: {
start: startMock,
status: statusMock,
logout: logoutMock,
},
}))
import { useHahaOpenAIOAuthStore } from './hahaOpenAIOAuthStore'
const initialState = useHahaOpenAIOAuthStore.getState()
describe('hahaOpenAIOAuthStore', () => {
beforeEach(() => {
vi.useFakeTimers()
startMock.mockReset()
statusMock.mockReset()
logoutMock.mockReset()
useHahaOpenAIOAuthStore.setState({
...initialState,
status: null,
isPolling: false,
isLoading: false,
error: null,
})
})
afterEach(() => {
useHahaOpenAIOAuthStore.getState().stopPolling()
useHahaOpenAIOAuthStore.setState(initialState)
vi.useRealTimers()
})
it('login returns authorizeUrl without starting polling', async () => {
startMock.mockResolvedValue({
authorizeUrl: 'http://localhost:3456/callback/openai?state=openai-state',
state: 'openai-state',
})
const result = await useHahaOpenAIOAuthStore.getState().login()
expect(result.authorizeUrl).toContain('/callback/openai')
expect(useHahaOpenAIOAuthStore.getState().isPolling).toBe(false)
})
it('startPolling stops after OpenAI OAuth status becomes logged in', async () => {
statusMock
.mockResolvedValueOnce({ loggedIn: false })
.mockResolvedValueOnce({
loggedIn: true,
expiresAt: Date.now() + 60_000,
email: 'user@example.com',
accountId: 'acct_123',
})
useHahaOpenAIOAuthStore.getState().startPolling()
expect(useHahaOpenAIOAuthStore.getState().isPolling).toBe(true)
await vi.advanceTimersByTimeAsync(2_000)
expect(useHahaOpenAIOAuthStore.getState().isPolling).toBe(true)
await vi.advanceTimersByTimeAsync(2_000)
expect(useHahaOpenAIOAuthStore.getState().status).toMatchObject({
loggedIn: true,
email: 'user@example.com',
accountId: 'acct_123',
})
expect(useHahaOpenAIOAuthStore.getState().isPolling).toBe(false)
})
it('logout clears status and stops polling', async () => {
logoutMock.mockResolvedValue({ ok: true })
useHahaOpenAIOAuthStore.setState({
status: {
loggedIn: true,
expiresAt: Date.now() + 60_000,
email: 'user@example.com',
accountId: 'acct_123',
},
})
useHahaOpenAIOAuthStore.getState().startPolling()
await useHahaOpenAIOAuthStore.getState().logout()
expect(logoutMock).toHaveBeenCalledTimes(1)
expect(useHahaOpenAIOAuthStore.getState().status).toEqual({ loggedIn: false })
expect(useHahaOpenAIOAuthStore.getState().isPolling).toBe(false)
})
})