File size: 5,160 Bytes
4c76b0d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Auth choice contract tests cover provider auth choice metadata and setup behavior.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { clearRuntimeAuthProfileStoreSnapshots } from "../../agents/auth-profiles/runtime-snapshots.js";
import { resolvePreferredProviderForAuthChoice } from "../../plugins/provider-auth-choice-preference.js";
import { buildProviderPluginMethodChoice } from "../provider-wizard.js";
import type { ProviderPlugin } from "../types.js";

type ResolvePluginProviders =
  typeof import("../../plugins/provider-auth-choice.runtime.js").resolvePluginProviders;
type ResolveProviderPluginChoice =
  typeof import("../../plugins/provider-auth-choice.runtime.js").resolveProviderPluginChoice;
type RunProviderModelSelectedHook =
  typeof import("../../plugins/provider-auth-choice.runtime.js").runProviderModelSelectedHook;
const resolvePluginProvidersMock = vi.hoisted(() => vi.fn<ResolvePluginProviders>(() => []));
const resolvePluginSetupProviderMock = vi.hoisted(() => vi.fn(() => undefined));
const resolveProviderPluginChoiceMock = vi.hoisted(() => vi.fn<ResolveProviderPluginChoice>());
const runProviderModelSelectedHookMock = vi.hoisted(() =>
  vi.fn<RunProviderModelSelectedHook>(async () => {}),
);
const runAuthMethodMock = vi.hoisted(() => vi.fn(async () => ({ profiles: [] })));

vi.mock("../../plugins/provider-auth-choice.runtime.js", () => ({
  resolvePluginProviders: resolvePluginProvidersMock,
  resolvePluginSetupProvider: resolvePluginSetupProviderMock,
  resolveProviderPluginChoice: resolveProviderPluginChoiceMock,
  runProviderModelSelectedHook: runProviderModelSelectedHookMock,
}));

function createAuthChoiceProvider(params: {
  providerId: string;
  label: string;
  methodId: string;
  methodLabel: string;
  kind: "oauth" | "api_key" | "custom";
}) {
  return {
    id: params.providerId,
    label: params.label,
    auth: [
      {
        id: params.methodId,
        label: params.methodLabel,
        hint:
          params.kind === "api_key"
            ? "Paste key"
            : params.kind === "custom"
              ? "No auth"
              : "Browser sign-in",
        kind: params.kind,
        run: runAuthMethodMock,
      },
    ],
  } satisfies ProviderPlugin;
}

async function expectPreferredProviderFallback(provider: ProviderPlugin) {
  resolvePluginProvidersMock.mockClear();
  resolvePluginProvidersMock.mockReturnValue([provider]);
  await expect(
    resolvePreferredProviderForAuthChoice({
      choice: buildProviderPluginMethodChoice(provider.id, provider.auth[0]?.id ?? "default"),
    }),
  ).resolves.toBe(provider.id);
  expect(resolvePluginProvidersMock).toHaveBeenCalled();
}

describe("provider auth-choice contract", () => {
  beforeEach(() => {
    resolvePluginProvidersMock.mockReset();
    resolvePluginProvidersMock.mockReturnValue([]);
    resolveProviderPluginChoiceMock.mockReset();
    resolveProviderPluginChoiceMock.mockImplementation(({ providers, choice }) => {
      const provider = providers.find((entry) =>
        entry.auth.some(
          (method) => buildProviderPluginMethodChoice(entry.id, method.id) === choice,
        ),
      );
      if (!provider) {
        return null;
      }
      const method =
        provider.auth.find(
          (entry) => buildProviderPluginMethodChoice(provider.id, entry.id) === choice,
        ) ?? null;
      return method ? { provider, method } : null;
    });
  });

  afterEach(async () => {
    vi.restoreAllMocks();
    resolvePluginProvidersMock.mockReset();
    resolvePluginProvidersMock.mockReturnValue([]);
    resolveProviderPluginChoiceMock.mockReset();
    resolveProviderPluginChoiceMock.mockReturnValue(null);
    runProviderModelSelectedHookMock.mockReset();
    clearRuntimeAuthProfileStoreSnapshots();
  });

  it("maps provider-plugin choices through the shared preferred-provider fallback resolver", async () => {
    const pluginFallbackScenarios: ProviderPlugin[] = [
      createAuthChoiceProvider({
        providerId: "demo-oauth-provider",
        label: "Demo OAuth Provider",
        methodId: "oauth",
        methodLabel: "OAuth",
        kind: "oauth",
      }),
      createAuthChoiceProvider({
        providerId: "demo-browser-provider",
        label: "Demo Browser Provider",
        methodId: "portal",
        methodLabel: "Portal",
        kind: "oauth",
      }),
      createAuthChoiceProvider({
        providerId: "demo-api-key-provider",
        label: "Demo API Key Provider",
        methodId: "api-key",
        methodLabel: "API key",
        kind: "api_key",
      }),
      createAuthChoiceProvider({
        providerId: "demo-local-provider",
        label: "Demo Local Provider",
        methodId: "local",
        methodLabel: "Local",
        kind: "custom",
      }),
    ];

    for (const provider of pluginFallbackScenarios) {
      await expectPreferredProviderFallback(provider);
    }

    resolvePluginProvidersMock.mockClear();
    await expect(resolvePreferredProviderForAuthChoice({ choice: "unknown" })).resolves.toBe(
      undefined,
    );
    expect(resolvePluginProvidersMock).toHaveBeenCalled();
  });
});