cc-haha/src/server/__tests__/haha-oauth-api.test.ts
程序员阿江(Relakkes) 7421db2857 fix(desktop): align haha-oauth handler with sibling error-handling convention
Code review follow-up on 142eb9cd:
- 加 outer try/catch + errorResponse wrapper (parity with providers.ts)
- 400 响应改用 ApiError.badRequest (shape 跟其他 handler 一致: {error: 'BAD_REQUEST', message})
- test 加上对 error shape 的断言

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:06:22 +08:00

128 lines
4.1 KiB
TypeScript

/**
* Integration tests for /api/haha-oauth/* endpoints.
*/
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
import * as fs from 'fs/promises'
import * as path from 'path'
import * as os from 'os'
import { handleHahaOAuthApi } from '../api/haha-oauth.js'
import { hahaOAuthService } from '../services/hahaOAuthService.js'
let tmpDir: string
let originalConfigDir: string | undefined
async function setup() {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'haha-oauth-api-test-'))
originalConfigDir = process.env.CLAUDE_CONFIG_DIR
process.env.CLAUDE_CONFIG_DIR = tmpDir
}
async function teardown() {
if (originalConfigDir === undefined) {
delete process.env.CLAUDE_CONFIG_DIR
} else {
process.env.CLAUDE_CONFIG_DIR = originalConfigDir
}
await fs.rm(tmpDir, { recursive: true, force: true })
}
function buildReq(
method: string,
pathname: string,
body?: unknown,
): { req: Request; url: URL; segments: string[] } {
const url = new URL(`http://localhost:3456${pathname}`)
const req = new Request(url.toString(), {
method,
headers: body ? { 'Content-Type': 'application/json' } : undefined,
body: body ? JSON.stringify(body) : undefined,
})
const segments = url.pathname.split('/').filter(Boolean)
return { req, url, segments }
}
describe('POST /api/haha-oauth/start', () => {
beforeEach(setup)
afterEach(teardown)
test('returns authorize URL with PKCE challenge', async () => {
const { req, url, segments } = buildReq('POST', '/api/haha-oauth/start', {
serverPort: 54321,
})
const res = await handleHahaOAuthApi(req, url, segments)
expect(res.status).toBe(200)
const data = (await res.json()) as { authorizeUrl: string; state: string }
expect(data.authorizeUrl).toContain('code_challenge_method=S256')
expect(data.authorizeUrl).toContain(
encodeURIComponent('http://localhost:54321/api/haha-oauth/callback'),
)
expect(data.state).toMatch(/^[A-Za-z0-9_-]+$/)
})
test('400 if serverPort missing', async () => {
const { req, url, segments } = buildReq('POST', '/api/haha-oauth/start', {})
const res = await handleHahaOAuthApi(req, url, segments)
expect(res.status).toBe(400)
const body = (await res.json()) as { error: string; message?: string }
expect(body.error).toBe('BAD_REQUEST')
})
})
describe('GET /api/haha-oauth/status', () => {
beforeEach(setup)
afterEach(teardown)
test('returns loggedIn=false when no token file', async () => {
const { req, url, segments } = buildReq('GET', '/api/haha-oauth/status')
const res = await handleHahaOAuthApi(req, url, segments)
expect(res.status).toBe(200)
const data = (await res.json()) as { loggedIn: boolean }
expect(data.loggedIn).toBe(false)
})
test('returns loggedIn=true + metadata when token saved', async () => {
await hahaOAuthService.saveTokens({
accessToken: 'sk-ant-oat01-xxx',
refreshToken: 'sk-ant-ort01-xxx',
expiresAt: Date.now() + 3600_000,
scopes: ['user:inference'],
subscriptionType: 'max',
})
const { req, url, segments } = buildReq('GET', '/api/haha-oauth/status')
const res = await handleHahaOAuthApi(req, url, segments)
expect(res.status).toBe(200)
const data = (await res.json()) as {
loggedIn: boolean
subscriptionType: string | null
scopes: string[]
}
expect(data.loggedIn).toBe(true)
expect(data.subscriptionType).toBe('max')
expect(data.scopes).toEqual(['user:inference'])
expect(JSON.stringify(data)).not.toContain('sk-ant-oat01')
expect(JSON.stringify(data)).not.toContain('sk-ant-ort01')
})
})
describe('DELETE /api/haha-oauth', () => {
beforeEach(setup)
afterEach(teardown)
test('clears token file', async () => {
await hahaOAuthService.saveTokens({
accessToken: 'a',
refreshToken: null,
expiresAt: null,
scopes: [],
subscriptionType: null,
})
const { req, url, segments } = buildReq('DELETE', '/api/haha-oauth')
const res = await handleHahaOAuthApi(req, url, segments)
expect(res.status).toBe(200)
expect(await hahaOAuthService.loadTokens()).toBeNull()
})
})