mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-20 15:25:50 +00:00
chore: 移除 ultrareview preflight stub 及其测试
- 删除 src/services/api/ultrareviewPreflight.ts(自动生成的 stub) - 删除 src/commands/review/UltrareviewPreflightDialog.tsx(依赖前者的 UI stub) - 删除 src/services/api/__tests__/ultrareviewPreflight.test.ts(测试已删代码) - 同步移除 ultrareviewCommand.test.tsx 中对 UltrareviewPreflightDialog 的 mock Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Box, Dialog, Text } from '@anthropic/ink';
|
||||
import { Select } from '../../components/CustomSelect/select.js';
|
||||
|
||||
type Props = {
|
||||
billingNote: string | null;
|
||||
onConfirm: (signal: AbortSignal) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog shown when /v1/ultrareview/preflight returns action='confirm'.
|
||||
* Displays the server-provided billing_note (or a generic fallback) and
|
||||
* gives the user a Proceed / Cancel choice.
|
||||
*/
|
||||
export function UltrareviewPreflightDialog({ billingNote, onConfirm, onCancel }: Props): React.ReactNode {
|
||||
const [isLaunching, setIsLaunching] = useState(false);
|
||||
const abortControllerRef = useRef(new AbortController());
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(value: string) => {
|
||||
if (value === 'proceed') {
|
||||
setIsLaunching(true);
|
||||
void onConfirm(abortControllerRef.current.signal).catch(() => setIsLaunching(false));
|
||||
} else {
|
||||
onCancel();
|
||||
}
|
||||
},
|
||||
[onConfirm, onCancel],
|
||||
);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
abortControllerRef.current.abort();
|
||||
onCancel();
|
||||
}, [onCancel]);
|
||||
|
||||
const options = [
|
||||
{ label: 'Proceed', value: 'proceed' },
|
||||
{ label: 'Cancel', value: 'cancel' },
|
||||
];
|
||||
|
||||
const displayNote = billingNote ?? 'This run may incur additional cost.';
|
||||
|
||||
return (
|
||||
<Dialog title="Ultrareview — additional cost" onCancel={handleCancel} color="background">
|
||||
<Box flexDirection="column" gap={1}>
|
||||
<Text>{displayNote}</Text>
|
||||
{isLaunching ? (
|
||||
<Text color="background">Launching…</Text>
|
||||
) : (
|
||||
<Select options={options} onChange={handleSelect} onCancel={handleCancel} />
|
||||
)}
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -179,13 +179,10 @@ mock.module('src/components/CustomSelect/select.js', () => ({
|
||||
Select: 'Select',
|
||||
}));
|
||||
|
||||
// UltrareviewOverageDialog and PreflightDialog — return a simple marker
|
||||
// UltrareviewOverageDialog — return a simple marker
|
||||
mock.module('src/commands/review/UltrareviewOverageDialog.js', () => ({
|
||||
UltrareviewOverageDialog: () => ({ type: 'UltrareviewOverageDialog' }),
|
||||
}));
|
||||
mock.module('src/commands/review/UltrareviewPreflightDialog.js', () => ({
|
||||
UltrareviewPreflightDialog: () => ({ type: 'UltrareviewPreflightDialog' }),
|
||||
}));
|
||||
|
||||
import { call } from '../ultrareviewCommand.js';
|
||||
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
/**
|
||||
* Regression tests for fetchUltrareviewPreflight.
|
||||
* Verifies all three action enum states (proceed/confirm/blocked),
|
||||
* network/HTTP error handling, and Zod schema mismatch fallback.
|
||||
*/
|
||||
import { afterAll, beforeAll, describe, expect, mock, test } from 'bun:test'
|
||||
import { debugMock } from '../../../../tests/mocks/debug.js'
|
||||
import { logMock } from '../../../../tests/mocks/log.js'
|
||||
import { setupAxiosMock } from '../../../../tests/mocks/axios.js'
|
||||
|
||||
// Mock dependency chain before any subject import
|
||||
mock.module('src/utils/debug.ts', debugMock)
|
||||
mock.module('src/utils/log.ts', logMock)
|
||||
mock.module('src/services/analytics/index.js', () => ({
|
||||
logEvent: () => {},
|
||||
}))
|
||||
|
||||
// Mock auth utilities
|
||||
mock.module('src/utils/auth.js', () => ({
|
||||
isClaudeAISubscriber: () => true,
|
||||
isTeamSubscriber: () => false,
|
||||
isEnterpriseSubscriber: () => false,
|
||||
}))
|
||||
|
||||
// Mock OAuth config
|
||||
mock.module('src/constants/oauth.js', () => ({
|
||||
getOauthConfig: () => ({ BASE_API_URL: 'https://api.anthropic.com' }),
|
||||
}))
|
||||
|
||||
// Mock prepareApiRequest and getOAuthHeaders
|
||||
mock.module('src/utils/teleport/api.js', () => ({
|
||||
prepareApiRequest: async () => ({
|
||||
accessToken: 'test-token',
|
||||
orgUUID: 'org-uuid-test',
|
||||
}),
|
||||
getOAuthHeaders: (token: string) => ({
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'anthropic-version': '2023-06-01',
|
||||
}),
|
||||
}))
|
||||
|
||||
// We'll mock axios at module level.
|
||||
// Typed as any in test code (CLAUDE.md: mock data may use as any).
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockAxiosPost = mock(async (..._args: any[]): Promise<any> => {
|
||||
throw new Error('not configured')
|
||||
})
|
||||
|
||||
const axiosHandle = setupAxiosMock()
|
||||
axiosHandle.stubs.post = mockAxiosPost
|
||||
axiosHandle.stubs.isAxiosError = (e: unknown) =>
|
||||
typeof e === 'object' &&
|
||||
e !== null &&
|
||||
(e as { isAxiosError?: boolean }).isAxiosError === true
|
||||
|
||||
beforeAll(() => {
|
||||
axiosHandle.useStubs = true
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
axiosHandle.useStubs = false
|
||||
})
|
||||
|
||||
import {
|
||||
fetchUltrareviewPreflight,
|
||||
type UltrareviewPreflightResponse,
|
||||
} from '../ultrareviewPreflight.js'
|
||||
|
||||
describe('fetchUltrareviewPreflight', () => {
|
||||
test('returns proceed action when server responds with proceed', async () => {
|
||||
const serverResponse: UltrareviewPreflightResponse = {
|
||||
action: 'proceed',
|
||||
billing_note: null,
|
||||
}
|
||||
mockAxiosPost.mockImplementationOnce(async () => ({
|
||||
status: 200,
|
||||
data: serverResponse,
|
||||
}))
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.action).toBe('proceed')
|
||||
expect(result?.billing_note).toBeNull()
|
||||
})
|
||||
|
||||
test('returns confirm action with billing_note when server responds with confirm', async () => {
|
||||
const serverResponse: UltrareviewPreflightResponse = {
|
||||
action: 'confirm',
|
||||
billing_note: 'This run will cost approximately $2.50.',
|
||||
}
|
||||
mockAxiosPost.mockImplementationOnce(async () => ({
|
||||
status: 200,
|
||||
data: serverResponse,
|
||||
}))
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.action).toBe('confirm')
|
||||
expect(result?.billing_note).toBe('This run will cost approximately $2.50.')
|
||||
})
|
||||
|
||||
test('returns blocked action when server responds with blocked', async () => {
|
||||
const serverResponse: UltrareviewPreflightResponse = {
|
||||
action: 'blocked',
|
||||
billing_note: null,
|
||||
}
|
||||
mockAxiosPost.mockImplementationOnce(async () => ({
|
||||
status: 200,
|
||||
data: serverResponse,
|
||||
}))
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.action).toBe('blocked')
|
||||
})
|
||||
|
||||
test('returns null on schema mismatch (invalid action value)', async () => {
|
||||
mockAxiosPost.mockImplementationOnce(async () => ({
|
||||
status: 200,
|
||||
data: { action: 'unknown_action', billing_note: null },
|
||||
}))
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
test('returns null on network error (no response)', async () => {
|
||||
const networkError = new Error('ECONNREFUSED')
|
||||
;(networkError as unknown as { isAxiosError: boolean }).isAxiosError = true
|
||||
mockAxiosPost.mockImplementationOnce(async () => {
|
||||
throw networkError
|
||||
})
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
test('returns null on 401 Unauthorized', async () => {
|
||||
const authError = new Error('Unauthorized')
|
||||
;(
|
||||
authError as unknown as {
|
||||
isAxiosError: boolean
|
||||
response: { status: number }
|
||||
}
|
||||
).isAxiosError = true
|
||||
;(authError as unknown as { response: { status: number } }).response = {
|
||||
status: 401,
|
||||
}
|
||||
mockAxiosPost.mockImplementationOnce(async () => {
|
||||
throw authError
|
||||
})
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
test('returns null on 403 Forbidden', async () => {
|
||||
const forbiddenError = new Error('Forbidden')
|
||||
;(
|
||||
forbiddenError as unknown as {
|
||||
isAxiosError: boolean
|
||||
response: { status: number }
|
||||
}
|
||||
).isAxiosError = true
|
||||
;(forbiddenError as unknown as { response: { status: number } }).response =
|
||||
{ status: 403 }
|
||||
mockAxiosPost.mockImplementationOnce(async () => {
|
||||
throw forbiddenError
|
||||
})
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
test('returns null on 5xx server error', async () => {
|
||||
const serverError = new Error('Internal Server Error')
|
||||
;(
|
||||
serverError as unknown as {
|
||||
isAxiosError: boolean
|
||||
response: { status: number }
|
||||
}
|
||||
).isAxiosError = true
|
||||
;(serverError as unknown as { response: { status: number } }).response = {
|
||||
status: 500,
|
||||
}
|
||||
mockAxiosPost.mockImplementationOnce(async () => {
|
||||
throw serverError
|
||||
})
|
||||
|
||||
const result = await fetchUltrareviewPreflight({ repo: 'owner/repo' })
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
test('passes pr_number to request body when provided', async () => {
|
||||
mockAxiosPost.mockImplementationOnce(
|
||||
async (_url: unknown, body: unknown) => {
|
||||
const b = body as { pr_number: number }
|
||||
expect(b.pr_number).toBe(42)
|
||||
return { status: 200, data: { action: 'proceed', billing_note: null } }
|
||||
},
|
||||
)
|
||||
|
||||
const result = await fetchUltrareviewPreflight({
|
||||
repo: 'owner/repo',
|
||||
pr_number: 42,
|
||||
})
|
||||
expect(result?.action).toBe('proceed')
|
||||
})
|
||||
|
||||
test('passes confirm flag to request body when provided', async () => {
|
||||
mockAxiosPost.mockImplementationOnce(
|
||||
async (_url: unknown, body: unknown) => {
|
||||
const b = body as { confirm: boolean }
|
||||
expect(b.confirm).toBe(true)
|
||||
return { status: 200, data: { action: 'proceed', billing_note: null } }
|
||||
},
|
||||
)
|
||||
|
||||
const result = await fetchUltrareviewPreflight({
|
||||
repo: 'owner/repo',
|
||||
confirm: true,
|
||||
})
|
||||
expect(result?.action).toBe('proceed')
|
||||
})
|
||||
})
|
||||
@@ -1,81 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import z from 'zod/v4'
|
||||
import { getOauthConfig } from '../../constants/oauth.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js'
|
||||
|
||||
/**
|
||||
* Zod schema for the /v1/ultrareview/preflight response.
|
||||
* Based on binary-extracted schema: vq.object({action: vq.enum([...]), billing_note: ...})
|
||||
*/
|
||||
const UltrareviewPreflightSchema = z.object({
|
||||
action: z.enum(['proceed', 'confirm', 'blocked']),
|
||||
billing_note: z.string().nullable().optional(),
|
||||
})
|
||||
|
||||
export type UltrareviewPreflightResponse = z.infer<
|
||||
typeof UltrareviewPreflightSchema
|
||||
>
|
||||
|
||||
export type UltrareviewPreflightArgs = {
|
||||
repo: string
|
||||
pr_number?: number
|
||||
pr_url?: string
|
||||
confirm?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /v1/ultrareview/preflight — server-side gate before launch.
|
||||
*
|
||||
* Returns the preflight result (proceed / confirm / blocked) or null on any
|
||||
* failure (network error, auth error, schema mismatch). Callers must treat
|
||||
* null as "fallback to direct launch" to preserve existing behavior.
|
||||
*
|
||||
* The `confirm` flag should be set to true when the user has already
|
||||
* acknowledged the billing dialog (or passed --confirm on the CLI), which
|
||||
* skips the server-side confirm prompt and gets a direct proceed/blocked.
|
||||
*/
|
||||
export async function fetchUltrareviewPreflight(
|
||||
args: UltrareviewPreflightArgs,
|
||||
): Promise<UltrareviewPreflightResponse | null> {
|
||||
try {
|
||||
const { accessToken, orgUUID } = await prepareApiRequest()
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
repo: args.repo,
|
||||
}
|
||||
if (args.pr_number !== undefined) {
|
||||
body.pr_number = args.pr_number
|
||||
}
|
||||
if (args.pr_url !== undefined) {
|
||||
body.pr_url = args.pr_url
|
||||
}
|
||||
if (args.confirm !== undefined) {
|
||||
body.confirm = args.confirm
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
`${getOauthConfig().BASE_API_URL}/v1/ultrareview/preflight`,
|
||||
body,
|
||||
{
|
||||
headers: {
|
||||
...getOAuthHeaders(accessToken),
|
||||
'x-organization-uuid': orgUUID,
|
||||
},
|
||||
timeout: 10000,
|
||||
},
|
||||
)
|
||||
|
||||
const parsed = UltrareviewPreflightSchema.safeParse(response.data)
|
||||
if (!parsed.success) {
|
||||
logForDebugging(
|
||||
`fetchUltrareviewPreflight: schema mismatch — ${parsed.error.message}`,
|
||||
)
|
||||
return null
|
||||
}
|
||||
return parsed.data
|
||||
} catch (error) {
|
||||
logForDebugging(`fetchUltrareviewPreflight failed: ${error}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user