mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 19:02:17 +03:00
* fix(ci): clear the base-reds the 2026-08-30 afternoon merge batch left on release/v3.8.51 (round 5)
- docs-counts / check-docs-counts-sync test: #12103 (Perplexity Agent) made it 352
providers; README, AGENTS.md, llm.txt (+42 i18n mirrors), package.json description
and the 4 README diagrams still said 351.
- api-route-typecheck: #11971 passes a third `{ featureEnabled }` argument to
appendNoThinkingVariants() that the helper never accepted (TS2554 — and the flag
silently did nothing); the helper now honours it. src/lib/skills/interception.ts
narrowed a mapped object with a `Record<string, string>` predicate (TS2677) —
predicate typed with the actual element shape.
Gates: check:docs-counts OK (test 28/28), check:docs-sync PASS, check:api-typecheck
OK (289 frozen). Refs #12103, #11971
* docs(env): document RATE_LIMIT_EXECUTION_MAX_WAIT_MS (#12027 added it to .env.example only)
* fix(ci): round 5b — freeze the react-hooks compiler-rule violations, align 7 tests to merged contracts
No new ESLint warnings: the exact CI command (lint:json --max-warnings 0) reports 278
problems on the tip — 226 from eslint-plugin-react-hooks 7 compiler rules
(set-state-in-effect 167, immutability 36, refs/static-components/purity/
preserve-manual-memoization) that were masked until the lockfile change of
dfc84ba030 invalidated the ESLint cache, plus 46 no-explicit-any in
tests/unit/call-log-cap.test.ts (#12026). Velocity phase: frozen with
`eslint --suppress-all` (+668 suppressions); the 5 now-unused
`eslint-disable react-hooks/immutability` directives and one unused import removed.
Verified: lint:json --max-warnings 0 → 0 problems.
Tests aligned to contracts merged this afternoon (all reproduced red on the pure tip):
- providers-constants-split: 235 → 236 (Perplexity Agent, #12103)
- sse-auth: a forced pin outside allowedConnections now yields no credential
instead of silently falling back (#12080)
- with-chat-admission-10786: withInjectionGuard(postHandler, { logger: null }) (#12117)
- hard-session-lease-bypass-inventory: classify src/app/api/oauth/codex/import/route.ts (#12116)
- usage-service-hardening: OpenCode Go official usage API shape (#12124)
- i18n placeholder parity: apiManager.restrictedToConnections rewritten as a plain
ICU plural (`{count, plural, one {# connection} other {# connections}}`) in en,
vi, pt-BR and the 40 __MISSING__ mirrors — the parity extractor counts every
`{word}` including the old literal `{s}`
Refs #12103, #12080, #12117, #12116, #12124, #12026
* fix(ci): run the ESLint warnings job on the box with an 8 GB heap; reserved-prefix set 398 → 400
The cold full lint with the react-hooks 7 compiler rules is killed on the 7 GB hosted
runner with no message (status null → exit 1, JSON never written) — it only looked
green while the ESLint cache was warm. tests/unit/provider-node-reserved-prefix.test.ts
aligned to the two prefixes the afternoon batch registered (#12103).
* test(ci): document the lint-guard runner exception; #9147 event-loop gap 400 → 800 ms
quality-rail-gate-membership pinned lint-guard to ubuntu-latest; the cold full lint is
OOM-killed there, so the job now runs on omni-light with an 8 GB heap — the test keeps
fast-gates pinned and asserts the documented exception. With the catalog at 352
providers the hosted shards measure 410–633 ms gaps on 9147-catalog-eventloop-yield
(3 runs); 800 ms still fails a true pin. Re-tighten with the v4.0 catalog split.
* chore(quality): summarize the ESLint report on failure — a red lint:json printed nothing
--format json --output-file swallows every problem; a red 'No new ESLint warnings' job
gave zero output (three blind debugging rounds in #12144), and a killed process (OOM,
status null) was equally silent. On any non-zero exit the runner now prints the problem
count and the first 60 'file:line rule — message' lines from the report.
* chore(lint): freeze react-hooks/immutability for the 5 UI test harnesses in the suppressions file
The rule fires for these files in CI but not locally (compiler analysis divergence),
so the inline eslint-disable directives read as 'unused directive' warnings locally.
A suppressions entry is symmetric: suppressed where the rule fires, tolerated as
unpruned (--pass-on-unpruned-suppressions) where it does not. Found via the new
lint:json failure summary.
308 lines
9.1 KiB
TypeScript
308 lines
9.1 KiB
TypeScript
// @vitest-environment jsdom
|
|
// tests/unit/ui/use-tools-builder.test.tsx
|
|
// Runs via Vitest (vitest.config.ts)
|
|
// Uses React DOM directly (no @testing-library/dom dep required).
|
|
import React, { act, useRef } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { describe, it, expect } from "vitest";
|
|
import {
|
|
useToolsBuilder,
|
|
} from "../../../src/app/(dashboard)/dashboard/playground/hooks/useToolsBuilder";
|
|
import type { ToolDefinition } from "../../../src/lib/playground/codeExport";
|
|
|
|
// ─── Minimal hook test harness ────────────────────────────────────────────────
|
|
|
|
type HookResult<T> = { current: T };
|
|
|
|
function mountHook<T>(useHook: () => T): {
|
|
hookRef: HookResult<T>;
|
|
unmount: () => void;
|
|
} {
|
|
const hookRef: HookResult<T> = { current: undefined as unknown as T };
|
|
const container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
const root = createRoot(container);
|
|
|
|
function HookComponent() {
|
|
const captureRef = useRef<T>(undefined as unknown as T);
|
|
captureRef.current = useHook();
|
|
hookRef.current = captureRef.current;
|
|
return null;
|
|
}
|
|
|
|
act(() => {
|
|
root.render(React.createElement(HookComponent));
|
|
});
|
|
|
|
return {
|
|
hookRef,
|
|
unmount: () => {
|
|
act(() => root.unmount());
|
|
container.remove();
|
|
},
|
|
};
|
|
}
|
|
|
|
// ─── Test fixtures ────────────────────────────────────────────────────────────
|
|
|
|
const VALID_TOOL: ToolDefinition = {
|
|
type: "function",
|
|
function: {
|
|
name: "get_weather",
|
|
description: "Get the current weather",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
location: { type: "string" },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const VALID_TOOL_2: ToolDefinition = {
|
|
type: "function",
|
|
function: {
|
|
name: "search_web",
|
|
parameters: {},
|
|
},
|
|
};
|
|
|
|
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
describe("useToolsBuilder", () => {
|
|
describe("initial state", () => {
|
|
it("starts with empty tools and empty errors", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
expect(result.current.tools).toHaveLength(0);
|
|
expect(result.current.errors.size).toBe(0);
|
|
unmount();
|
|
});
|
|
});
|
|
|
|
describe("add()", () => {
|
|
it("returns { ok: false, error } when tool has empty name", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
const invalidTool = {
|
|
type: "function" as const,
|
|
function: { name: "", parameters: {} },
|
|
};
|
|
|
|
let outcome: ReturnType<typeof result.current.add> | undefined;
|
|
act(() => {
|
|
outcome = result.current.add(invalidTool as ToolDefinition);
|
|
});
|
|
|
|
expect(outcome).toMatchObject({ ok: false });
|
|
expect((outcome as { ok: false; error: string }).error).toBeTruthy();
|
|
expect(result.current.tools).toHaveLength(0);
|
|
unmount();
|
|
});
|
|
|
|
it("returns { ok: false } when type is not 'function'", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
const invalidTool = {
|
|
type: "not-function" as unknown as "function",
|
|
function: { name: "valid_name", parameters: {} },
|
|
};
|
|
|
|
let outcome: ReturnType<typeof result.current.add> | undefined;
|
|
act(() => {
|
|
outcome = result.current.add(invalidTool as ToolDefinition);
|
|
});
|
|
|
|
expect(outcome).toMatchObject({ ok: false });
|
|
expect(result.current.tools).toHaveLength(0);
|
|
unmount();
|
|
});
|
|
|
|
it("adds a valid tool and returns { ok: true }", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
let outcome: ReturnType<typeof result.current.add> | undefined;
|
|
act(() => {
|
|
outcome = result.current.add(VALID_TOOL);
|
|
});
|
|
|
|
expect(outcome).toMatchObject({ ok: true });
|
|
expect(result.current.tools).toHaveLength(1);
|
|
expect(result.current.tools[0].function.name).toBe("get_weather");
|
|
unmount();
|
|
});
|
|
|
|
it("adds multiple valid tools", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
result.current.add(VALID_TOOL_2);
|
|
});
|
|
|
|
expect(result.current.tools).toHaveLength(2);
|
|
unmount();
|
|
});
|
|
|
|
it("does not add to tools when validation fails", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
result.current.add({ type: "function", function: { name: "", parameters: {} } } as ToolDefinition);
|
|
});
|
|
|
|
expect(result.current.tools).toHaveLength(1);
|
|
unmount();
|
|
});
|
|
});
|
|
|
|
describe("remove()", () => {
|
|
it("removes tool at given index", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
result.current.add(VALID_TOOL_2);
|
|
});
|
|
|
|
act(() => {
|
|
result.current.remove(0);
|
|
});
|
|
|
|
expect(result.current.tools).toHaveLength(1);
|
|
expect(result.current.tools[0].function.name).toBe("search_web");
|
|
unmount();
|
|
});
|
|
|
|
it("is a no-op when index is out of bounds", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
});
|
|
|
|
act(() => {
|
|
result.current.remove(99);
|
|
});
|
|
|
|
expect(result.current.tools).toHaveLength(1);
|
|
unmount();
|
|
});
|
|
|
|
it("re-indexes errors after remove", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
result.current.add(VALID_TOOL_2);
|
|
// Trigger an error on index 1 via update with invalid tool
|
|
result.current.update(1, {
|
|
type: "function",
|
|
function: { name: "", parameters: {} },
|
|
} as ToolDefinition);
|
|
});
|
|
|
|
expect(result.current.errors.has(1)).toBe(true);
|
|
|
|
// Remove item at index 0
|
|
act(() => {
|
|
result.current.remove(0);
|
|
});
|
|
|
|
// Error for what was index 1 is now at index 0
|
|
expect(result.current.errors.has(0)).toBe(true);
|
|
expect(result.current.errors.has(1)).toBe(false);
|
|
unmount();
|
|
});
|
|
});
|
|
|
|
describe("update()", () => {
|
|
it("updates a tool at given index after validation", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
});
|
|
|
|
const updated: ToolDefinition = {
|
|
type: "function",
|
|
function: { name: "updated_fn", parameters: {} },
|
|
};
|
|
|
|
let outcome: ReturnType<typeof result.current.update> | undefined;
|
|
act(() => {
|
|
outcome = result.current.update(0, updated);
|
|
});
|
|
|
|
expect(outcome).toMatchObject({ ok: true });
|
|
expect(result.current.tools[0].function.name).toBe("updated_fn");
|
|
expect(result.current.errors.has(0)).toBe(false);
|
|
unmount();
|
|
});
|
|
|
|
it("returns { ok: false, error } and stores error when validation fails", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
});
|
|
|
|
let outcome: ReturnType<typeof result.current.update> | undefined;
|
|
act(() => {
|
|
outcome = result.current.update(0, {
|
|
type: "function",
|
|
function: { name: "", parameters: {} },
|
|
} as ToolDefinition);
|
|
});
|
|
|
|
expect(outcome).toMatchObject({ ok: false });
|
|
expect(result.current.errors.has(0)).toBe(true);
|
|
// Tool should not be changed
|
|
expect(result.current.tools[0].function.name).toBe("get_weather");
|
|
unmount();
|
|
});
|
|
|
|
it("clears error for that index on successful update", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
result.current.update(0, {
|
|
type: "function",
|
|
function: { name: "", parameters: {} },
|
|
} as ToolDefinition);
|
|
});
|
|
expect(result.current.errors.has(0)).toBe(true);
|
|
|
|
act(() => {
|
|
result.current.update(0, VALID_TOOL_2);
|
|
});
|
|
expect(result.current.errors.has(0)).toBe(false);
|
|
unmount();
|
|
});
|
|
});
|
|
|
|
describe("clear()", () => {
|
|
it("removes all tools and clears all errors", () => {
|
|
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
|
|
|
|
act(() => {
|
|
result.current.add(VALID_TOOL);
|
|
result.current.add(VALID_TOOL_2);
|
|
result.current.update(0, {
|
|
type: "function",
|
|
function: { name: "", parameters: {} },
|
|
} as ToolDefinition);
|
|
});
|
|
|
|
act(() => {
|
|
result.current.clear();
|
|
});
|
|
|
|
expect(result.current.tools).toHaveLength(0);
|
|
expect(result.current.errors.size).toBe(0);
|
|
unmount();
|
|
});
|
|
});
|
|
});
|