mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-08 07:03:22 +08:00
* feat(db): add pricing config fields to proxy_config table - Add default_cost_multiplier field per app type - Add pricing_model_source field (request/response) - Add request_model field to proxy_request_logs table - Implement schema migration v5 * feat(api): add pricing config commands and provider meta fields - Add get/set commands for default cost multiplier - Add get/set commands for pricing model source - Extend ProviderMeta with cost_multiplier and pricing_model_source - Register new commands in Tauri invoke handler * fix(proxy): apply cost multiplier to total cost only - Move multiplier calculation from per-item to total cost - Add resolve_pricing_config for provider-level override - Include request_model and cost_multiplier in usage logs - Return new fields in get_request_logs API * feat(ui): add pricing config UI and usage log enhancements - Add pricing config section to provider advanced settings - Refactor PricingConfigPanel to compact table layout - Display all three apps (Claude/Codex/Gemini) in one view - Add multiplier column and request model display to logs - Add frontend API wrappers for pricing config * feat(i18n): add pricing config translations - Add zh/en/ja translations for pricing defaults config - Add translations for multiplier, requestModel, responseModel - Add provider pricing config translations * fix(pricing): align backfill cost calculation with real-time logic - Fix backfill to deduct cache_read_tokens from input (avoid double billing) - Apply multiplier only to total cost, not to each item - Add multiplier display in request detail panel with i18n support - Use AppError::localized for backend error messages - Fix init_proxy_config_rows to use per-app default values - Fix silent failure in set_default_cost_multiplier/set_pricing_model_source - Add clippy allow annotation for test mutex across await * style: format code with cargo fmt and prettier * fix(tests): correct error type assertions in proxy DAO tests The tests expected AppError::InvalidInput but the DAO functions use AppError::localized() which returns AppError::Localized variant. Updated assertions to match the correct error type with key validation. --------- Co-authored-by: Jason <farion1231@gmail.com>
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||
import { GlobalProxySettings } from "@/components/settings/GlobalProxySettings";
|
||
|
||
vi.mock("react-i18next", () => ({
|
||
useTranslation: () => ({ t: (key: string) => key }),
|
||
}));
|
||
|
||
const mutateAsyncMock = vi.fn();
|
||
const testMutateAsyncMock = vi.fn();
|
||
const scanMutateAsyncMock = vi.fn();
|
||
|
||
vi.mock("@/hooks/useGlobalProxy", () => ({
|
||
useGlobalProxyUrl: () => ({ data: "http://127.0.0.1:7890", isLoading: false }),
|
||
useSetGlobalProxyUrl: () => ({
|
||
mutateAsync: mutateAsyncMock,
|
||
isPending: false,
|
||
}),
|
||
useTestProxy: () => ({
|
||
mutateAsync: testMutateAsyncMock,
|
||
isPending: false,
|
||
}),
|
||
useScanProxies: () => ({
|
||
mutateAsync: scanMutateAsyncMock,
|
||
isPending: false,
|
||
}),
|
||
}));
|
||
|
||
describe("GlobalProxySettings", () => {
|
||
beforeEach(() => {
|
||
mutateAsyncMock.mockReset();
|
||
testMutateAsyncMock.mockReset();
|
||
scanMutateAsyncMock.mockReset();
|
||
});
|
||
|
||
it("renders proxy URL input with saved value", async () => {
|
||
render(<GlobalProxySettings />);
|
||
|
||
const urlInput = screen.getByPlaceholderText(
|
||
"http://127.0.0.1:7890 / socks5://127.0.0.1:1080",
|
||
);
|
||
// URL 对象会在末尾添加斜杠
|
||
await waitFor(() =>
|
||
expect(urlInput).toHaveValue("http://127.0.0.1:7890/"),
|
||
);
|
||
});
|
||
|
||
it("saves proxy URL when save button is clicked", async () => {
|
||
render(<GlobalProxySettings />);
|
||
|
||
const urlInput = screen.getByPlaceholderText(
|
||
"http://127.0.0.1:7890 / socks5://127.0.0.1:1080",
|
||
);
|
||
|
||
fireEvent.change(urlInput, { target: { value: "http://localhost:8080" } });
|
||
|
||
const saveButton = screen.getByRole("button", { name: "common.save" });
|
||
fireEvent.click(saveButton);
|
||
|
||
await waitFor(() => expect(mutateAsyncMock).toHaveBeenCalled());
|
||
// 没有用户名时,URL 不经过 URL 对象解析,所以没有尾部斜杠
|
||
expect(mutateAsyncMock).toHaveBeenCalledWith("http://localhost:8080");
|
||
});
|
||
|
||
it("clears proxy URL when clear button is clicked", async () => {
|
||
render(<GlobalProxySettings />);
|
||
|
||
const urlInput = screen.getByPlaceholderText(
|
||
"http://127.0.0.1:7890 / socks5://127.0.0.1:1080",
|
||
);
|
||
|
||
// Wait for initial value to load
|
||
await waitFor(() =>
|
||
expect(urlInput).toHaveValue("http://127.0.0.1:7890/"),
|
||
);
|
||
|
||
// Click clear button
|
||
const clearButton = screen.getByTitle("settings.globalProxy.clear");
|
||
fireEvent.click(clearButton);
|
||
|
||
expect(urlInput).toHaveValue("");
|
||
});
|
||
});
|