provider-selector-resource-endpoints.test.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { beforeEach, describe, expect, test, vi } from "vitest";
  2. import type { Provider } from "@/types/provider";
  3. const circuitBreakerMocks = vi.hoisted(() => ({
  4. isCircuitOpen: vi.fn(async () => false),
  5. getCircuitState: vi.fn(() => "closed"),
  6. }));
  7. vi.mock("@/lib/circuit-breaker", () => circuitBreakerMocks);
  8. describe("ProxyProviderResolver.pickRandomProvider - resource endpoints without model", () => {
  9. beforeEach(() => {
  10. vi.clearAllMocks();
  11. });
  12. function createSessionStub(originalFormat: string, providers: Provider[]) {
  13. return {
  14. originalFormat,
  15. authState: null,
  16. getProvidersSnapshot: async () => providers,
  17. getOriginalModel: () => "",
  18. getCurrentModel: () => null,
  19. clientRequestsContext1m: () => false,
  20. } as any;
  21. }
  22. function createProvider(
  23. id: number,
  24. providerType: string,
  25. overrides: Partial<Provider> = {}
  26. ): Provider {
  27. return {
  28. id,
  29. name: `provider-${id}`,
  30. isEnabled: true,
  31. providerType,
  32. groupTag: null,
  33. weight: 1,
  34. priority: 0,
  35. costMultiplier: 1,
  36. allowedModels: ["guarded-model"],
  37. ...overrides,
  38. } as unknown as Provider;
  39. }
  40. async function setupResolverMocks() {
  41. const { ProxyProviderResolver } = await import("@/app/v1/_lib/proxy/provider-selector");
  42. vi.spyOn(ProxyProviderResolver as any, "filterByLimits").mockImplementation(
  43. async (...args: unknown[]) => args[0] as Provider[]
  44. );
  45. vi.spyOn(ProxyProviderResolver as any, "selectTopPriority").mockImplementation(
  46. (...args: unknown[]) => args[0] as Provider[]
  47. );
  48. vi.spyOn(ProxyProviderResolver as any, "selectOptimal").mockImplementation(
  49. (...args: unknown[]) => (args[0] as Provider[])[0] ?? null
  50. );
  51. return ProxyProviderResolver;
  52. }
  53. test("openai 资源端点在无 model 时仍应选择 openai-compatible provider", async () => {
  54. const ProxyProviderResolver = await setupResolverMocks();
  55. const incompatible = createProvider(1, "claude");
  56. const compatible = createProvider(2, "openai-compatible");
  57. const session = createSessionStub("openai", [incompatible, compatible]);
  58. const { provider, context } = await (ProxyProviderResolver as any).pickRandomProvider(
  59. session,
  60. []
  61. );
  62. expect(provider?.id).toBe(2);
  63. expect(provider?.providerType).toBe("openai-compatible");
  64. expect(context.requestedModel).toBe("");
  65. });
  66. test("response 资源端点在无 model 时仍应选择 codex provider", async () => {
  67. const ProxyProviderResolver = await setupResolverMocks();
  68. const incompatible = createProvider(1, "openai-compatible");
  69. const compatible = createProvider(2, "codex");
  70. const session = createSessionStub("response", [incompatible, compatible]);
  71. const { provider, context } = await (ProxyProviderResolver as any).pickRandomProvider(
  72. session,
  73. []
  74. );
  75. expect(provider?.id).toBe(2);
  76. expect(provider?.providerType).toBe("codex");
  77. expect(context.requestedModel).toBe("");
  78. });
  79. test("gemini 资源端点在无 model 时仍应选择 gemini provider", async () => {
  80. const ProxyProviderResolver = await setupResolverMocks();
  81. const incompatible = createProvider(1, "gemini-cli");
  82. const compatible = createProvider(2, "gemini");
  83. const session = createSessionStub("gemini", [incompatible, compatible]);
  84. const { provider, context } = await (ProxyProviderResolver as any).pickRandomProvider(
  85. session,
  86. []
  87. );
  88. expect(provider?.id).toBe(2);
  89. expect(provider?.providerType).toBe("gemini");
  90. expect(context.requestedModel).toBe("");
  91. });
  92. });