Ctrl K
ComunidadGuías…

lib/byok-connect-panel.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// @vitest-environment jsdom

import { act, createElement, Fragment } from "react";
import type * as React from "react";
import { createRoot, type Root } from "react-dom/client";
import { NextIntlClientProvider } from "next-intl";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ByokConnectPanel } from "@/components/settings/byok-connect-panel";
import { RuntimeConfigProvider } from "@/lib/runtime-config-provider";
import messages from "@/messages/en.json";

vi.mock("@/components/model-logo", () => ({
  ProviderLogo: () => null,
}));

const keyState = vi.hoisted(() => ({ keys: [] as import("@/lib/agent-keys-api").AiKey[] }));
const deleteKey = vi.hoisted(() => vi.fn());
vi.mock("@/lib/agent-keys-api", () => ({ deleteAiKeyApi: deleteKey, addAiKeyApi: vi.fn() }));

vi.mock("@/lib/use-ai-keys-query", () => ({
  aiKeysQueryKey: ["account", "ai-keys"],
  useAiKeysQuery: () => ({ keys: keyState.keys, loading: false }),
}));

vi.mock("sonner", async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  toast: { error: vi.fn(), success: vi.fn() },
}));
vi.mock("@/components/ui/spinner", async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  Spinner: () => null,
}));
vi.mock("@/components/ui/input", async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  Input: (props: React.InputHTMLAttributes<HTMLInputElement>) =>
    createElement("input", props),
}));
vi.mock("@/components/ui/select", async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  Select: ({
    children,
    value,
    onValueChange,
    disabled,
  }: React.PropsWithChildren<{
    value: string;
    onValueChange: (value: string) => void;
    disabled?: boolean;
  }>) =>
    createElement(
      "select",
      {
        value,
        disabled,
        onChange: (event: React.ChangeEvent<HTMLSelectElement>) =>
          onValueChange(event.target.value),
      },
      children,
    ),
  SelectTrigger: () => null,
  SelectValue: () => null,
  SelectContent: ({ children }: React.PropsWithChildren) =>
    createElement(Fragment, null, children),
  SelectGroup: ({ children }: React.PropsWithChildren) =>
    createElement(Fragment, null, children),
  SelectLabel: () => null,
  SelectSeparator: () => null,
  SelectItem: ({
    children,
    value,
  }: React.PropsWithChildren<{ value: string }>) =>
    createElement("option", { value }, children),
}));

describe("ByokConnectPanel", () => {
  let container: HTMLDivElement;
  let root: Root;

  beforeEach(() => {
    vi.stubGlobal("matchMedia", vi.fn(() => ({ matches: false, addEventListener: vi.fn(), removeEventListener: vi.fn() })));
    keyState.keys = [];
    deleteKey.mockReset().mockResolvedValue(undefined);
    (window as typeof window & { IS_REACT_ACT_ENVIRONMENT: boolean })
      .IS_REACT_ACT_ENVIRONMENT = true;
    container = document.createElement("div");
    document.body.append(container);
    root = createRoot(container);
  });

  afterEach(() => {
    act(() => root.unmount());
    container.remove();
    vi.restoreAllMocks();
    vi.unstubAllGlobals();
    delete (window as typeof window & { IS_REACT_ACT_ENVIRONMENT?: boolean })
      .IS_REACT_ACT_ENVIRONMENT;
  });

  async function renderPanel(managedAi = true) {
    const queryClient = new QueryClient({
      defaultOptions: { queries: { retry: false } },
    });
    await act(async () => {
      root.render(
        createElement(QueryClientProvider, {
          client: queryClient,
          children: createElement(RuntimeConfigProvider, {
            config: {
              appUrl: "https://www.minddy.app",
              supabaseUrl: "https://supabase.example.com",
              supabaseAnonKey: "anon-key",
              siteName: "minddy",
              contactEmail: "support@example.com",
              productFeedbackIntegrationEnabled: false,
              documentationFeedbackIntegrationEnabled: false,
              productFeedbackUrl: null,
              posthog: {
                key: null,
                host: null,
                allowLocalhost: false,
                errorTracking: false,
              },
              vapidPublicKey: null,
              capabilities: {
                managedAi: {
                  state: managedAi ? "available" : "disabled",
                  configured: managedAi,
                },
              },
            },
            children: createElement(NextIntlClientProvider, {
              locale: "en",
              messages,
              children: createElement(ByokConnectPanel),
            }),
          }),
        }),
      );
    });
  }

  it("defaults to minddy Cloud without showing BYOK fields", async () => {
    await renderPanel();

    expect(container.querySelector("select")?.value).toBe("minddy");
    expect(container.textContent).toContain("minddy Cloud");
    expect(container.textContent).toContain("No API key is needed");
    expect(container.querySelector('input[type="password"]')).toBeNull();
    expect(container.textContent).not.toContain("Save key");
  });

  it("shows BYOK fields only after selecting an external provider", async () => {
    await renderPanel();
    const select = container.querySelector("select");
    if (!select) throw new Error("Missing provider selector");

    await act(async () => {
      select.value = "openrouter";
      select.dispatchEvent(new Event("change", { bubbles: true }));
    });

    expect(container.querySelector("select")?.value).toBe("openrouter");
    expect(container.querySelector('input[type="password"]')).not.toBeNull();
    expect(container.textContent).toContain("Save key");
    expect(container.textContent).not.toContain("No API key is needed");
  });

  it("defaults to an external provider when managed AI is unavailable", async () => {
    await renderPanel(false);

    const select = container.querySelector("select");
    expect(select?.value).toBe("openrouter");
    expect(select?.querySelector('option[value="minddy"]')).toBeNull();
    expect(container.querySelector('input[type="password"]')).not.toBeNull();
    expect(container.textContent).toContain("Save key");
    expect(container.textContent).not.toContain("minddy Cloud");
  });
  it("requires confirmation before removing a key and allows cancellation", async () => {
    keyState.keys = [{ id: "key-1", provider: "openrouter", key_prefix: "sk-test", base_url: null, validated_at: "2026-10-07" } as import("@/lib/agent-keys-api").AiKey];
    await renderPanel();
    const remove = [...container.querySelectorAll("button")].find((button) => button.textContent === "Remove")!;
    await act(async () => remove.click());
    expect(deleteKey).not.toHaveBeenCalled();
    let dialog = document.querySelector('[role="alertdialog"]')!;
    expect(dialog.textContent).toContain("OpenRouter");
    await act(async () => [...dialog.querySelectorAll<HTMLButtonElement>("button")].find((button) => button.textContent === "Cancel")!.click());
    expect(deleteKey).not.toHaveBeenCalled();
    await act(async () => remove.click());
    dialog = document.querySelector('[role="alertdialog"]')!;
    await act(async () => [...dialog.querySelectorAll<HTMLButtonElement>("button")].find((button) => button.textContent === "Remove")!.click());
    expect(deleteKey).toHaveBeenCalledExactlyOnceWith("key-1");
    expect(document.querySelector('[role="alertdialog"]')).toBeNull();
  });

});