| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import { describe, expect, it } from "vitest";
- import type { Provider } from "@/types/provider";
- import { ProxyProviderResolver } from "@/app/v1/_lib/proxy/provider-selector";
- function makeProvider(overrides: Partial<Provider>): Provider {
- return {
- id: 1,
- name: "test",
- url: "https://api.example.com",
- key: "sk-test",
- providerVendorId: null,
- isEnabled: true,
- weight: 1,
- priority: 0,
- groupPriorities: null,
- costMultiplier: 1,
- groupTag: null,
- providerType: "claude",
- preserveClientIp: false,
- modelRedirects: null,
- allowedModels: null,
- mcpPassthroughType: "none",
- mcpPassthroughUrl: null,
- limit5hUsd: null,
- limitDailyUsd: null,
- dailyResetMode: "fixed",
- dailyResetTime: "00:00",
- limitWeeklyUsd: null,
- limitMonthlyUsd: null,
- limitTotalUsd: null,
- totalCostResetAt: null,
- limitConcurrentSessions: 0,
- maxRetryAttempts: null,
- circuitBreakerFailureThreshold: 5,
- circuitBreakerOpenDuration: 1800000,
- circuitBreakerHalfOpenSuccessThreshold: 2,
- proxyUrl: null,
- proxyFallbackToDirect: false,
- firstByteTimeoutStreamingMs: 30000,
- streamingIdleTimeoutMs: 10000,
- requestTimeoutNonStreamingMs: 600000,
- websiteUrl: null,
- faviconUrl: null,
- cacheTtlPreference: null,
- context1mPreference: null,
- codexReasoningEffortPreference: null,
- codexReasoningSummaryPreference: null,
- codexTextVerbosityPreference: null,
- codexParallelToolCallsPreference: null,
- anthropicMaxTokensPreference: null,
- anthropicThinkingBudgetPreference: null,
- geminiGoogleSearchPreference: null,
- tpm: null,
- rpm: null,
- rpd: null,
- cc: null,
- createdAt: new Date(),
- updatedAt: new Date(),
- ...overrides,
- };
- }
- describe("resolveEffectivePriority", () => {
- it("returns global priority when no groupPriorities", () => {
- const provider = makeProvider({ priority: 5, groupPriorities: null });
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "cli")).toBe(5);
- });
- it("returns group-specific priority when override exists", () => {
- const provider = makeProvider({
- priority: 5,
- groupPriorities: { cli: 0, chat: 2 },
- });
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "cli")).toBe(0);
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "chat")).toBe(2);
- });
- it("falls back to global when group not in overrides", () => {
- const provider = makeProvider({
- priority: 5,
- groupPriorities: { cli: 0 },
- });
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "chat")).toBe(5);
- });
- it("returns global priority when userGroup is null", () => {
- const provider = makeProvider({
- priority: 5,
- groupPriorities: { cli: 0 },
- });
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, null)).toBe(5);
- });
- it("handles group priority of 0 correctly (not falsy)", () => {
- const provider = makeProvider({
- priority: 5,
- groupPriorities: { cli: 0 },
- });
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "cli")).toBe(0);
- });
- it("handles comma-separated user groups (multi-group)", () => {
- const provider = makeProvider({
- priority: 10,
- groupPriorities: { cli: 2, admin: 5, chat: 8 },
- });
- // Multi-group "cli,admin" should match both and take minimum (2)
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "cli,admin")).toBe(2);
- // Multi-group "admin,chat" should take minimum (5)
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "admin,chat")).toBe(5);
- });
- it("falls back to global when no group in multi-group matches", () => {
- const provider = makeProvider({
- priority: 10,
- groupPriorities: { cli: 2 },
- });
- // "admin,chat" has no matching overrides, should fall back to global (10)
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "admin,chat")).toBe(10);
- });
- it("handles partial match in multi-group", () => {
- const provider = makeProvider({
- priority: 10,
- groupPriorities: { cli: 3 },
- });
- // "cli,admin" - only "cli" matches, should return 3
- expect(ProxyProviderResolver.resolveEffectivePriority(provider, "cli,admin")).toBe(3);
- });
- });
- describe("selectTopPriority with group context", () => {
- // Access private method via bracket notation for testing
- const selectTopPriority = (providers: Provider[], userGroup?: string | null) =>
- (ProxyProviderResolver as any).selectTopPriority(providers, userGroup);
- it("selects providers by group-aware priority", () => {
- const providerA = makeProvider({
- id: 1,
- name: "A",
- priority: 5,
- groupPriorities: { cli: 0 },
- });
- const providerB = makeProvider({
- id: 2,
- name: "B",
- priority: 0,
- groupPriorities: null,
- });
- // cli group: A has effective priority 0, B has effective priority 0
- const result = selectTopPriority([providerA, providerB], "cli");
- expect(result).toHaveLength(2);
- expect(result.map((p: Provider) => p.id).sort()).toEqual([1, 2]);
- });
- it("without group context, uses global priority", () => {
- const providerA = makeProvider({
- id: 1,
- name: "A",
- priority: 5,
- groupPriorities: { cli: 0 },
- });
- const providerB = makeProvider({
- id: 2,
- name: "B",
- priority: 0,
- groupPriorities: null,
- });
- // no group: A has priority 5, B has priority 0 -> only B selected
- const result = selectTopPriority([providerA, providerB], null);
- expect(result).toHaveLength(1);
- expect(result[0].id).toBe(2);
- });
- it("group override changes which providers are top priority", () => {
- const providerA = makeProvider({
- id: 1,
- name: "A",
- priority: 5,
- groupPriorities: { chat: 1 },
- });
- const providerB = makeProvider({
- id: 2,
- name: "B",
- priority: 3,
- groupPriorities: null,
- });
- // chat group: A=1, B=3 -> only A
- const chatResult = selectTopPriority([providerA, providerB], "chat");
- expect(chatResult).toHaveLength(1);
- expect(chatResult[0].id).toBe(1);
- // no group: A=5, B=3 -> only B
- const noGroupResult = selectTopPriority([providerA, providerB], null);
- expect(noGroupResult).toHaveLength(1);
- expect(noGroupResult[0].id).toBe(2);
- });
- });
|