mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
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
98 lines
2.8 KiB
TypeScript
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)
|
|
})
|
|
})
|