Files
OmniRoute/tests/unit/ui/rerank-config-card.test.tsx
diegosouzapw 516a7e5520 fix(memory): code-review hardening — guards, asserts, useEffect
Smaller fixes from the 2nd code-review pass on plan 21.

Backend / CLI / DB:
- memoryTools.ts: error-path fallback no longer hardcodes
  retrievalStrategy:"exact"; uses DEFAULT_MEMORY_SETTINGS via
  toMemoryRetrievalConfig (D16 / Bug #7).
- memory.mjs: applyLegacyTypeMap also runs on search / list / clear
  (was only on add); legacy user/feedback/project/reference remap to
  canonical types with a stderr warning (D17 / Bug #4).
- migrationRunner.ts: case "073" guards via
  hasColumn(memories, needs_reindex) so an unmarked re-run of
  073_memory_vec.sql is skipped cleanly (D27).

UI:
- MemoryEngineStatus: optional onConfigure callback; "Configurar →"
  CTAs on the Embedding / Qdrant / Rerank rows when those components
  are off or missing (matches §4.3 wireframe).
- EngineTab: scroll IDs on config cards + handleConfigure wired to
  the status panel. Providers fetch moved from render body
  (setState-during-render anti-pattern) into useEffect.
- RerankConfigCard: toggle is disabled when no provider has a key —
  blocks turning rerank ON without a provider, still allows turning
  it OFF (D13).
- MemoriesTab: Import validates each entry against the canonical
  type enum before POST so invalid types are caught locally with a
  clear skipped count.

Tooling:
- package.json: test:all includes test:vitest:ui so the UI suite
  is no longer orphaned in CI.

Tests:
- cli-memory-commands: asserts updated for the new legacy->canonical
  remap on search/clear.
- memory-embedding-resolve: drop always-true `|| reason.length > 0`
  clauses that neutralized two assertions.
- memory-embedding-static-potion: model_load_failed test forces a
  real load failure via MEMORY_STATIC_CACHE_DIR=/dev/null/<subdir>
  and asserts EmbeddingError shape + reason + sanitized message
  (replaces the previous `assert.ok(true)`).
- rerank-config-card.test.tsx: happy-path now uses a provider with
  hasKey; new test covers the disabled-toggle guard.

Full memory suite green: 331/331 unit tests, 46/46 UI tests.
typecheck:core, typecheck:noimplicit:core, check:cycles clean.
2026-05-28 21:46:54 -03:00

286 lines
9.2 KiB
TypeScript

// @vitest-environment jsdom
import React from "react";
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
const cleanupCallbacks: Array<() => void> = [];
function makeContainer(): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
cleanupCallbacks.push(() => container.remove());
return container;
}
const defaultSettings = {
embeddingSource: "auto" as const,
embeddingProviderModel: null,
transformersEnabled: false,
staticEnabled: false,
rerankEnabled: false,
rerankProviderModel: null,
};
describe("RerankConfigCard", () => {
beforeEach(() => {
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
true;
});
afterEach(() => {
while (cleanupCallbacks.length > 0) cleanupCallbacks.pop()?.();
document.body.innerHTML = "";
vi.clearAllMocks();
});
it("renders the enable toggle", async () => {
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={defaultSettings}
providers={[]}
onSave={vi.fn().mockResolvedValue(true)}
/>,
);
});
expect(container.querySelector("[data-testid='rerank-enabled-switch']")).toBeTruthy();
});
// Plan 21 D13 fix: providers with hasKey=true are now required to enable
// rerank. The "happy path" test must pass a configured provider; a separate
// test (below) covers the new guard that blocks enabling without a provider.
const providersWithKey = [
{
provider: "cohere",
hasKey: true,
models: [
{ id: "cohere/rerank-english-v3.0", name: "Rerank English v3", dimensions: null },
],
},
];
it("toggle switch calls onSave with rerankEnabled=true when disabled", async () => {
const onSave = vi.fn().mockResolvedValue(true);
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={defaultSettings}
providers={providersWithKey}
onSave={onSave}
/>,
);
});
const toggleBtn = container.querySelector(
"[data-testid='rerank-enabled-switch']",
) as HTMLButtonElement | null;
expect(toggleBtn).toBeTruthy();
await act(async () => {
toggleBtn?.click();
});
expect(onSave).toHaveBeenCalledWith({ rerankEnabled: true });
});
it("toggle is blocked when no provider has a key (rerankEnabled=false)", async () => {
// Plan 21 D13 fix: trying to enable rerank without a configured provider
// must NOT call onSave (the guard prevents the state from becoming inconsistent).
const onSave = vi.fn().mockResolvedValue(true);
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={defaultSettings}
providers={[]}
onSave={onSave}
/>,
);
});
const toggleBtn = container.querySelector(
"[data-testid='rerank-enabled-switch']",
) as HTMLButtonElement | null;
expect(toggleBtn).toBeTruthy();
expect(toggleBtn?.hasAttribute("disabled")).toBe(true);
await act(async () => {
toggleBtn?.click();
});
expect(onSave).not.toHaveBeenCalled();
});
it("toggle switch calls onSave with rerankEnabled=false when enabled", async () => {
const onSave = vi.fn().mockResolvedValue(true);
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={{ ...defaultSettings, rerankEnabled: true }}
providers={[]}
onSave={onSave}
/>,
);
});
const toggleBtn = container.querySelector(
"[data-testid='rerank-enabled-switch']",
) as HTMLButtonElement | null;
expect(toggleBtn).toBeTruthy();
await act(async () => {
toggleBtn?.click();
});
expect(onSave).toHaveBeenCalledWith({ rerankEnabled: false });
});
it("shows warning banner when rerankEnabled=true", async () => {
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={{ ...defaultSettings, rerankEnabled: true }}
providers={[]}
onSave={vi.fn().mockResolvedValue(true)}
/>,
);
});
expect(container.textContent).toContain("rerank.warning");
});
it("does not show warning banner when rerankEnabled=false", async () => {
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={defaultSettings}
providers={[]}
onSave={vi.fn().mockResolvedValue(true)}
/>,
);
});
expect(container.textContent).not.toContain("rerank.warning");
});
it("shows no-provider warning when rerankEnabled=true but no providers have keys", async () => {
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={{ ...defaultSettings, rerankEnabled: true }}
providers={[{ provider: "cohere", hasKey: false, models: [] }]}
onSave={vi.fn().mockResolvedValue(true)}
/>,
);
});
expect(container.querySelector("[data-testid='rerank-no-provider-warning']")).toBeTruthy();
expect(container.textContent).toContain("rerank.noProviderWithKey");
});
it("shows provider select (not warning) when rerankEnabled=true and provider with key exists", async () => {
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const providers = [
{
provider: "cohere",
hasKey: true,
models: [
{ id: "cohere/rerank-english-v3", name: "rerank-english-v3", dimensions: 0 },
],
},
];
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={{ ...defaultSettings, rerankEnabled: true }}
providers={providers}
onSave={vi.fn().mockResolvedValue(true)}
/>,
);
});
// No warning
expect(container.querySelector("[data-testid='rerank-no-provider-warning']")).toBeNull();
// Provider/model select present
expect(container.querySelector("[data-testid='rerank-provider-model-select']")).toBeTruthy();
// Model name visible
expect(container.textContent).toContain("rerank-english-v3");
});
it("selecting a model calls onSave with rerankProviderModel", async () => {
const onSave = vi.fn().mockResolvedValue(true);
const providers = [
{
provider: "cohere",
hasKey: true,
models: [
{ id: "cohere/rerank-english-v3", name: "rerank-english-v3", dimensions: 0 },
],
},
];
const { default: RerankConfigCard } = await import(
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard"
);
const container = makeContainer();
const root = createRoot(container);
await act(async () => {
root.render(
<RerankConfigCard
settings={{ ...defaultSettings, rerankEnabled: true }}
providers={providers}
onSave={onSave}
/>,
);
});
const select = container.querySelector(
"[data-testid='rerank-provider-model-select']",
) as HTMLSelectElement | null;
expect(select).toBeTruthy();
await act(async () => {
if (select) {
const nativeSetter = Object.getOwnPropertyDescriptor(
window.HTMLSelectElement.prototype,
"value",
)?.set;
nativeSetter?.call(select, "cohere/rerank-english-v3");
select.dispatchEvent(new Event("change", { bubbles: true }));
}
});
expect(onSave).toHaveBeenCalledWith({ rerankProviderModel: "cohere/rerank-english-v3" });
});
});