Files
OmniRoute/tests/unit/ui/engineConfigPage.test.tsx
Diego Rodrigues de Sa e Souza 95e6522720 feat(compression): unified config panel — single source for engine on/off + level (Phase 1) (#4432)
* docs(compression): design spec for the unified compression config panel

Engine-centric central panel (single source for master + per-engine on/off + level),
default pipeline derived from the engines map; Combos page owns named ordered pipelines +
active-profile selection; per-engine pages keep only detailed config. Phased: (1) core
consolidation + Model A migration, (2) named profiles + active selector, (3) per-request
x-omniroute-compression header.

* docs(compression): Phase 1 implementation plan for the unified config panel

12 TDD tasks: engine catalog, engines map + activeComboId, deriveDefaultPlan,
migration 102 + backfill, resolveCompressionPlan, runtime wiring, API, default-combo
shim, the engine-grid panel, consolidation, menu. Ends with a recap + the pending
follow-ups (Phase 2 named profiles/active selector, Phase 3 header, deferred items).

* feat(compression): engine catalog metadata (levels, single-mode, order)

* feat(compression): add engines map + activeComboId to CompressionConfig

* feat(compression): deriveDefaultPlan (engines map -> mode/pipeline)

Pure function that converts a per-engine EngineToggle map + masterEnabled
flag into a { mode, stackedPipeline } plan: off when master is off or no
engines on; single-mode when exactly one single-mode engine is enabled;
stacked (sorted by stackPriority) otherwise.

* feat(compression): persist+backfill engines map and activeComboId (migration 102)

* feat(compression): resolveCompressionPlan precedence resolver (header>override>active>default)

* feat(compression): selectCompressionStrategy uses resolveCompressionPlan

* feat(api): settings/compression carries engines map + activeComboId

Extends compressionSettingsUpdateSchema with engines (Record<string,{enabled:boolean,level?:string}>)
and activeComboId (string|null) so the PUT route accepts and persists these fields.
GET already returns the full getCompressionSettings() object which includes both fields.

TDD: added route round-trip tests (PUT+GET) for engines, activeComboId, null clear,
and schema rejection of invalid engines shape.

* refactor(compression): default-combo route is a read-only shim (default derived from engines)

* feat(dashboard): engine-grid compression panel (single source for on/off + level)

* refactor(dashboard): remove duplicate compression toggles; per-engine pages keep only detailed config

* feat(compression): unified panel menu order + derived-pipeline integration coverage

* fix(compression): import ENGINE_IDS via types re-export so it resolves under vitest

The bare "@omniroute/open-sse/.../engineCatalog.ts" specifier resolves under tsc/tsx
but not under vitest's MCP config: Vite externalizes a brand-new open-sse module to
Node, which can't load the .ts subpath. types.ts is already in Vite's graph, so route
ENGINE_IDS through its re-export. Fixes 3 failing MCP vitest suites (cacheTools,
dbHealthTool, essentialTools).

* fix(compression): engines map drives dispatch only when explicitly panel-saved

Code-review finding: the legacy seeded default combo (present on every install via
migration 042/043) was silently overriding a panel-configured engines map — the
default-combo block in chatCore set compressionComboApplied=true, skipping the
engines-map override, so an operator's panel toggles were ignored.

Gate the engines-map path on a new runtime-only CompressionConfig.enginesExplicit
(true when a stored engines row exists). Panel-saved installs: the engines map is
authoritative (deriveDefaultPlanFromConfig + new enginesMapDerivesStackedPipeline
guard the chatCore default-combo block). Legacy/backfilled installs: the map is
display-only and dispatch stays on the historical defaultMode/default-combo path —
zero behaviour change until the operator opts in via the panel.

Also documents why resolveCacheAwareConfig's getCacheAwareStrategy(config.defaultMode)
arg is safe (skipSystemPrompt is mode-independent).

* docs(compression): add MDX frontmatter + escape inline brace to fix fumadocs build

docs/compression/**/*.md is compiled as MDX by the Next build (fumadocs,
source.config.ts). The two planning docs lacked the required `title`
frontmatter (build error: 'title: expected string, received undefined') and the
design doc had a bare {rtk,caveman} that MDX parsed as a JSX expression. Adds
frontmatter matching the sibling docs and backticks the brace. Fixes the
dast-smoke build step.
2026-06-20 19:07:39 -03:00

421 lines
14 KiB
TypeScript

// @vitest-environment jsdom
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
// ── Helpers ───────────────────────────────────────────────────────────────
const containers: HTMLElement[] = [];
const roots: Array<{ unmount: () => void }> = [];
function mountInContainer(ui: React.ReactElement): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
containers.push(container);
const root = createRoot(container);
roots.push(root);
act(() => {
root.render(ui);
});
return container;
}
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
});
afterEach(async () => {
// Restore mocks first so any in-flight fetch promises settle without blocking
vi.restoreAllMocks();
await act(async () => {
while (roots.length > 0) {
roots.pop()?.unmount();
}
});
// Drain all remaining microtasks from effects that fired during unmount
for (let i = 0; i < 10; i++) {
await Promise.resolve();
}
while (containers.length > 0) {
containers.pop()?.remove();
}
document.body.innerHTML = "";
});
// ── Mock fetch ────────────────────────────────────────────────────────────
const ENGINE_PAYLOAD = {
engines: [
{
id: "headroom",
name: "Headroom",
description: "Headroom engine description",
icon: "🗜️",
stackable: true,
stackPriority: 1,
metadata: { description: "Headroom metadata description" },
configSchema: [
{
key: "enabled",
type: "boolean",
label: "Enabled",
defaultValue: true,
},
{
key: "minRows",
type: "number",
label: "Min rows",
defaultValue: 8,
min: 1,
max: 1000,
},
],
},
],
};
const COMBO_PAYLOAD = {
id: "default",
name: "Default",
description: "Default combo",
pipeline: [],
languagePacks: [],
outputMode: null,
};
const ANALYTICS_PAYLOAD = {
engineId: "headroom",
runs: 0,
tokensSaved: 0,
avgSavingsPercent: 0,
days: 7,
};
const SETTINGS_PAYLOAD = {
enabled: true,
engines: { headroom: { enabled: true } },
aggressive: {
summarizerEnabled: true,
maxTokensPerMessage: 2048,
minSavingsThreshold: 0.05,
},
};
function setupFetchMock() {
vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => {
const url = input.toString();
if (url.includes("/api/compression/engines")) {
return new Response(JSON.stringify(ENGINE_PAYLOAD), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url.includes("/api/settings/compression")) {
return new Response(JSON.stringify(SETTINGS_PAYLOAD), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url.includes("/api/context/combos/default")) {
return new Response(JSON.stringify(COMBO_PAYLOAD), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url.includes("/api/context/analytics/engine")) {
return new Response(JSON.stringify(ANALYTICS_PAYLOAD), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url.includes("/api/compression/preview")) {
return new Response(
JSON.stringify({
original: "The original context contains duplicated details and verbose wording.",
compressed: "Original context, deduplicated.",
originalTokens: 11,
compressedTokens: 4,
savingsPct: 63.6,
diff: [{ type: "removed", text: "duplicated details and verbose wording" }],
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
return new Response(JSON.stringify({}), { status: 404 });
});
}
// ── Tests ─────────────────────────────────────────────────────────────────
describe("EngineConfigPage", () => {
it("renders the engine name after fetching engine list", async () => {
setupFetchMock();
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
// Flush any pending microtasks from effects
await act(async () => {
await Promise.resolve();
});
expect(container.textContent).toContain("Headroom");
});
it("does NOT render an engine on/off enable toggle (moved to the panel)", async () => {
setupFetchMock();
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
await act(async () => {
await Promise.resolve();
});
// The on/off enable control now lives only in the panel (/dashboard/context/settings).
expect(container.querySelector("[data-toggle='enable']")).toBeNull();
expect(container.textContent).not.toContain("Enable layer");
});
it("renders the config form field label from fetched schema (EngineConfigForm mounted)", async () => {
setupFetchMock();
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
await act(async () => {
await Promise.resolve();
});
// EngineConfigForm should render the "Min rows" field label from the schema
expect(container.textContent).toContain("Min rows");
});
it("keeps detailed config but renders no engine enable checkbox", async () => {
setupFetchMock();
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
await act(async () => {
await Promise.resolve();
});
// The on/off enable toggle (a checkbox with data-toggle="enable") is gone; the
// detailed config form (the schema fields minus `enabled`) still renders.
expect(container.querySelector("input[type='checkbox'][data-toggle='enable']")).toBeNull();
expect(container.textContent).toContain("Min rows");
expect(container.textContent).toContain("Configuration");
});
it("renders preview original, compressed text, and diff returned by the API", async () => {
setupFetchMock();
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
await act(async () => {
await Promise.resolve();
});
const previewButton = Array.from(container.querySelectorAll("button")).find(
(button) => button.textContent === "Preview"
);
expect(previewButton).toBeTruthy();
await act(async () => {
previewButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
await Promise.resolve();
});
expect(container.textContent).toContain(
"The original context contains duplicated details and verbose wording."
);
expect(container.textContent).toContain("Original context, deduplicated.");
expect(container.textContent).toContain("Diff");
expect(container.textContent).toContain("duplicated details and verbose wording");
});
it("shows empty-state text when analytics returns runs=0", async () => {
setupFetchMock();
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
await act(async () => {
await Promise.resolve();
});
// Should show some "no data" copy
const hasEmptyState =
container.textContent?.includes("Sem dados") === true ||
container.textContent?.includes("No data") === true;
expect(hasEmptyState).toBe(true);
});
it("points to the Compression Settings panel for enabling the layer", async () => {
setupFetchMock();
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
await act(async () => {
await Promise.resolve();
});
// The on/off + level live in the panel now; the page surfaces a link to it.
const settingsLink = container.querySelector('a[href="/dashboard/context/settings"]');
expect(settingsLink).not.toBeNull();
expect(container.textContent).toContain("Compression Settings");
});
it("INVARIANT #1: handleSave writes the detailed sub-object to settings/compression, never PUTs combos/default", async () => {
const AGGRESSIVE_PAYLOAD = {
engines: [
{
id: "aggressive",
name: "Aggressive",
description: "Aggressive engine",
icon: "🗜️",
stackable: true,
stackPriority: 30,
metadata: { description: "Aggressive metadata" },
configSchema: [
{ key: "enabled", type: "boolean", label: "Enabled", defaultValue: true },
{
key: "maxTokensPerMessage",
type: "number",
label: "Max tokens per message",
defaultValue: 2048,
},
],
},
],
};
const settingsPuts: { body: Record<string, unknown> }[] = [];
const comboWrites: { method: string }[] = [];
vi.spyOn(globalThis, "fetch").mockImplementation(
async (input: RequestInfo | URL, init?: RequestInit) => {
const url = input.toString();
if (url.includes("/api/compression/engines")) {
return new Response(JSON.stringify(AGGRESSIVE_PAYLOAD), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url.includes("/api/settings/compression")) {
if (init?.method === "PUT") {
settingsPuts.push({ body: JSON.parse(init.body as string) });
}
return new Response(
JSON.stringify({
enabled: true,
engines: { aggressive: { enabled: true } },
aggressive: { maxTokensPerMessage: 2048 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
if (url.includes("/api/context/combos/default")) {
// A PUT/POST here would violate INVARIANT #1 (the route is a 410 shim).
if (init?.method === "PUT" || init?.method === "POST") {
comboWrites.push({ method: init.method });
}
return new Response(JSON.stringify(COMBO_PAYLOAD), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url.includes("/api/context/analytics/engine")) {
return new Response(JSON.stringify(ANALYTICS_PAYLOAD), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({}), { status: 404 });
}
);
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="aggressive" />);
});
await act(async () => {
await Promise.resolve();
});
const salvarBtn = Array.from(container.querySelectorAll("button")).find(
(b) => b.textContent?.includes("Save") || b.textContent?.includes("Salvar")
);
expect(salvarBtn).toBeTruthy();
await act(async () => {
salvarBtn?.click();
});
await act(async () => {
await Promise.resolve();
});
// INVARIANT #1: no write ever lands on the deprecated default-combo route.
expect(comboWrites).toHaveLength(0);
// The detailed config persists to the engine's sub-object on settings/compression.
expect(settingsPuts.length).toBeGreaterThan(0);
const aggressivePut = settingsPuts.find(
(c) => typeof c.body.aggressive === "object" && c.body.aggressive !== null
);
expect(aggressivePut).toBeDefined();
});
it("does not crash when all fetch calls fail (fail-soft)", async () => {
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("Network error"));
const { EngineConfigPage } =
await import("../../../src/shared/components/compression/EngineConfigPage");
let container!: HTMLElement;
await act(async () => {
container = mountInContainer(<EngineConfigPage engineId="headroom" />);
});
await act(async () => {
await Promise.resolve();
});
// Component should still be mounted (not crashed)
expect(container).toBeTruthy();
expect(container.parentNode).toBeTruthy();
});
});