mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(codex): expose official routing and restore the built-in provider
Let users switch between the built-in OpenAI provider and third-party Codex providers directly from the provider panel while takeover mode remains active. Centralize the frontend capability predicate so only the fixed codex-official seed receives native-login routing support, and keep copied UUID-based official entries clearly marked as unsupported. Add an idempotent backend command that recreates the deleted official seed, wire it into the add-provider flow, refresh localized guidance, and add mutation and provider-action regression coverage.
This commit is contained in:
@@ -8,6 +8,7 @@ import type { Provider } from "@/types";
|
||||
const apiMocks = vi.hoisted(() => ({
|
||||
add: vi.fn(),
|
||||
ensureClaudeDesktopOfficialProvider: vi.fn(),
|
||||
ensureCodexOfficialProvider: vi.fn(),
|
||||
getAll: vi.fn(),
|
||||
updateTrayMenu: vi.fn(),
|
||||
}));
|
||||
@@ -21,6 +22,8 @@ vi.mock("@/lib/api", () => ({
|
||||
add: (...args: unknown[]) => apiMocks.add(...args),
|
||||
ensureClaudeDesktopOfficialProvider: (...args: unknown[]) =>
|
||||
apiMocks.ensureClaudeDesktopOfficialProvider(...args),
|
||||
ensureCodexOfficialProvider: (...args: unknown[]) =>
|
||||
apiMocks.ensureCodexOfficialProvider(...args),
|
||||
getAll: (...args: unknown[]) => apiMocks.getAll(...args),
|
||||
updateTrayMenu: (...args: unknown[]) => apiMocks.updateTrayMenu(...args),
|
||||
},
|
||||
@@ -59,6 +62,7 @@ beforeEach(() => {
|
||||
apiMocks.ensureClaudeDesktopOfficialProvider
|
||||
.mockReset()
|
||||
.mockResolvedValue(true);
|
||||
apiMocks.ensureCodexOfficialProvider.mockReset().mockResolvedValue(true);
|
||||
apiMocks.getAll.mockReset().mockResolvedValue({});
|
||||
apiMocks.updateTrayMenu.mockReset().mockResolvedValue(true);
|
||||
uuidMocks.generateUUID.mockReset().mockReturnValue("generated-uuid");
|
||||
@@ -133,4 +137,34 @@ describe("useAddProviderMutation", () => {
|
||||
expect(apiMocks.add).not.toHaveBeenCalled();
|
||||
expect(persistedProvider).toEqual(seedProvider);
|
||||
});
|
||||
|
||||
it("recreates and returns the fixed Codex official seed", async () => {
|
||||
const seedProvider: Provider = {
|
||||
id: "codex-official",
|
||||
name: "OpenAI Official",
|
||||
settingsConfig: { auth: {}, config: "" },
|
||||
category: "official",
|
||||
};
|
||||
apiMocks.getAll.mockResolvedValueOnce({
|
||||
"codex-official": seedProvider,
|
||||
});
|
||||
const { wrapper } = createWrapper();
|
||||
const { result } = renderHook(() => useAddProviderMutation("codex"), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
const persistedProvider = await act(async () =>
|
||||
result.current.mutateAsync({
|
||||
name: "OpenAI Official",
|
||||
settingsConfig: { auth: {}, config: "" },
|
||||
category: "official",
|
||||
ensureCodexOfficialSeed: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(apiMocks.ensureCodexOfficialProvider).toHaveBeenCalledTimes(1);
|
||||
expect(apiMocks.getAll).toHaveBeenCalledWith("codex");
|
||||
expect(apiMocks.add).not.toHaveBeenCalled();
|
||||
expect(persistedProvider).toEqual(seedProvider);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -264,6 +264,66 @@ describe("useProviderActions", () => {
|
||||
expect(switchProviderMutateAsync).toHaveBeenCalledWith(provider.id);
|
||||
});
|
||||
|
||||
it("allows the built-in Codex official provider during takeover", async () => {
|
||||
switchProviderMutateAsync.mockResolvedValueOnce(undefined);
|
||||
const { wrapper } = createWrapper();
|
||||
const provider = createProvider({
|
||||
id: "codex-official",
|
||||
category: "official",
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useProviderActions("codex", true, true),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.switchProvider(provider);
|
||||
});
|
||||
|
||||
expect(switchProviderMutateAsync).toHaveBeenCalledWith("codex-official");
|
||||
expect(toastErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("continues blocking other official providers during takeover", async () => {
|
||||
const { wrapper } = createWrapper();
|
||||
const provider = createProvider({
|
||||
id: "claude-official",
|
||||
category: "official",
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useProviderActions("claude", true, true),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.switchProvider(provider);
|
||||
});
|
||||
|
||||
expect(switchProviderMutateAsync).not.toHaveBeenCalled();
|
||||
expect(toastErrorMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not grant routing capability to a UUID Codex official copy", async () => {
|
||||
const { wrapper } = createWrapper();
|
||||
const provider = createProvider({
|
||||
id: "generated-uuid",
|
||||
category: "official",
|
||||
});
|
||||
const { result } = renderHook(
|
||||
() => useProviderActions("codex", true, true),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.switchProvider(provider);
|
||||
});
|
||||
|
||||
expect(switchProviderMutateAsync).not.toHaveBeenCalled();
|
||||
expect(toastErrorMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should sync plugin config when switching Claude provider with integration enabled", async () => {
|
||||
switchProviderMutateAsync.mockResolvedValueOnce(undefined);
|
||||
settingsApiGetMock.mockResolvedValueOnce({
|
||||
|
||||
Reference in New Issue
Block a user