Files
cc-switch/tests/lib/userAgent.test.ts
Jason 596019505f feat(provider-form): custom User-Agent presets dropdown in advanced settings
Polish the provider-level User-Agent override UI on the Claude and Codex forms.

- Add a shared CustomUserAgentField (label + input + preset dropdown + live
  validation) so both forms stay in sync.
- Provide curated UA presets (Claude Code / Kilo Code families that pass
  coding-plan UA whitelists per #3671); the first is Claude Code's real
  `claude-cli/x (external, cli)` format. Whitelists gate on the name prefix,
  not the version, so static values stay valid across upgrades.
- Expose presets via a dropdown to the right of the input (z-[200] so it
  renders above the dialog layers) instead of inline chips.
- Move the field into the existing advanced/reasoning collapsibles.
- userAgent.ts mirrors the backend byte rule (reject only control chars;
  non-ASCII is allowed) for a non-blocking inline hint.
- i18n for all four locales (zh/en/ja/zh-TW).
2026-06-11 08:29:29 +08:00

35 lines
1.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from "vitest";
import { isValidUserAgentHeader } from "@/lib/userAgent";
// 与后端 parse_custom_user_agent / http::HeaderValue::from_str 的字节级规则对齐:
// 合法 = b>=32 && b!=127 || b=='\t'。即仅控制字符(除 \t 外的 0x000x1F 与 0x7F非法
// 可见 ASCII 与非 ASCII 都合法。控制字符用 String.fromCharCode 构造,避免源码内嵌生字节。
const NUL = String.fromCharCode(0);
const DEL = String.fromCharCode(0x7f);
describe("isValidUserAgentHeader", () => {
it("treats empty / whitespace-only as valid (unset)", () => {
expect(isValidUserAgentHeader("")).toBe(true);
expect(isValidUserAgentHeader(" ")).toBe(true);
});
it("accepts visible ASCII (trimmed)", () => {
expect(isValidUserAgentHeader("claude-cli/2.1.161")).toBe(true);
expect(isValidUserAgentHeader(" claude-cli/2.1.161 ")).toBe(true);
});
it("accepts non-ASCII — matches backend HeaderValue byte rule", () => {
expect(isValidUserAgentHeader("claude-cli/1.0 中文")).toBe(true);
});
it("accepts internal tab", () => {
expect(isValidUserAgentHeader("claude\tcli")).toBe(true);
});
it("rejects control characters (newline / null / DEL)", () => {
expect(isValidUserAgentHeader("claude\ncli")).toBe(false);
expect(isValidUserAgentHeader(`claude${NUL}cli`)).toBe(false);
expect(isValidUserAgentHeader(`claude${DEL}cli`)).toBe(false);
});
});