Files
OmniRoute/tests/unit/batch-b-final.test.mjs
T
Diego Rodrigues de Sa e Souza 70a4d38d04
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Release v3.4.0 (Integration) (#861)
* test(settings): add unit tests for debugMode and hiddenSidebarItems

Tests cover:
- PATCH debugMode=true/false
- PATCH hiddenSidebarItems with array values
- Combined updates with both fields

* test(e2e): add Playwright tests for settings toggles

Tests cover:
- Debug mode toggle on/off
- Sidebar visibility toggle
- Settings persistence after page reload

* fix(tests): address code review issues

- Unit tests: fix async/await for getSettings, use direct db functions
- E2E tests: remove conditional logic, use Playwright auto-waiting assertions

* feat(logging): unify request log retention and artifacts

* docs: add dashboard settings toggles to CONTRIBUTING

Add section documenting:
- Debug Mode toggle (Settings → Advanced)
- Sidebar Visibility toggle (Settings → General)

* fix(cache): only inject prompt_cache_key for supported providers

Only inject prompt_cache_key for providers that support prompt caching
(Claude, Anthropic, ZAI, Qwen, DeepSeek). This fixes issue #848 where
NVIDIA API rejected the parameter.

* fix(model-sync): log only channel-level model changes

* feat(providers): add 4 free models to opencode-zen

* feat(providers): add explicit contextLength for opencode-zen free models

* feat(providers): add contextLength for all opencode-zen models

* feat: Improve the Chinese translation

* fix: preserve client cache_control for all Claude-protocol providers

Previously, the cache control preservation logic only recognized a
hardcoded list of providers (claude, anthropic, zai, qwen, deepseek).
This caused OmniRoute to inject its own cache_control markers for
Claude-protocol providers not in that list (bailian-coding-plan, glm,
minimax, minimax-cn, etc.), overwriting the client's cache markers.

The fix checks both:
1. Known caching providers list (existing behavior)
2. Whether targetFormat === 'claude' (all Claude-protocol providers)

This ensures all Claude-compatible providers properly preserve client
cache_control headers when appropriate (Claude Code client, deterministic
routing, etc.).

Also removes unused CacheStatsCard from settings/components (duplicate
of the one in cache/ page).

Fixes cache token calculation for GLM, Minimax, and other Claude-compatible providers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: pure passthrough for Claude→Claude when cache_control preserved

The Claude passthrough path round-trips through OpenAI format
(claude→openai→claude) for structural normalization. This strips
cache_control markers from every content block since OpenAI format
has no equivalent, causing ~42k cache creation tokens per request
with zero cache reads.

When preserveCacheControl is true (Claude Code client, "always"
setting, or deterministic combo), skip the round-trip entirely and
forward the body as-is. Claude Code sends well-formed Messages API
payloads — the normalization was only needed for non-Code clients.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restore CacheStatsCard — was not a duplicate

The first commit incorrectly deleted CacheStatsCard from
settings/components/ as a "duplicate". It's the only copy — both
settings/page.tsx and cache/page.tsx import from this location.

Restored the i18n-ized version from main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(429): parse long quota reset times from error body

- Parse XhYmZs format from antigravity error messages (e.g., 27h41m36s)
- Dynamic retry-after threshold (60s default) instead of hardcoded 10s
- Add parseRetryFromErrorText() in accountFallback.ts for body parsing
- Fix 403 'verify your account' to trigger permanent deactivation
- Add keyword matching for 'quota will reset', 'exhausted capacity'
- Add unit tests for retry parsing and keyword matching

Fixes #858 (Antigravity 429 handling)
Fixes #832 (Qwen quota 429 - same underlying bug)

* chore: bump version to v3.4.0-dev

* fix(migrations): rename 013 to 014 to avoid collision with v3.3.11

* chore(docs): update CHANGELOG for v3.4.0 integrations

* fix: Claude token refresh, Antigravity quota, and 429 rate-limit handling

- Fix Claude OAuth token refresh to use form-urlencoded format (standard OAuth2)
- Add anthropic-beta header required by Claude OAuth API
- Switch Antigravity quota to use retrieveUserQuota API (same as Gemini CLI)
- Parse quota reset time for all providers (not just Antigravity)
- Add quota reset keywords to error classifier
- Cap maximum retry time at 24 hours to prevent infinite wait

Closes #836, #857, #858, #832

* fix(dashboard): resolve /dashboard/limits hanging UI with 70+ accounts via chunk parallelization (#784)

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: R.D. <rogerproself@gmail.com>
Co-authored-by: kang-heewon <heewon.dev@gmail.com>
Co-authored-by: gmw <rorschach1167@qq.com>
Co-authored-by: tombii <github@tombii.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-03-31 10:22:52 -03:00

271 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Batch B — Final Tasks Tests
*
* Tests for: evalRunner, a11yAudit, responsiveSpecs, compliance (noLog)
*/
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
// ──────────────── T-42: Eval Runner ────────────────
import {
registerSuite,
getSuite,
listSuites,
evaluateCase,
runSuite,
createScorecard,
resetSuites,
} from "../../src/lib/evals/evalRunner.ts";
describe("evalRunner", () => {
after(() => {
// Re-register golden set since resetSuites clears everything
resetSuites();
});
it("should have golden-set suite pre-registered", () => {
const suite = getSuite("golden-set");
assert.ok(suite);
assert.equal(suite.name, "OmniRoute Golden Set");
assert.ok(suite.cases.length >= 10);
});
it("should list registered suites", () => {
const suites = listSuites();
assert.ok(suites.length >= 1);
assert.ok(suites.some((s) => s.id === "golden-set"));
});
it("should evaluate exact match", () => {
const result = evaluateCase(
{
id: "t1",
name: "test",
model: "test",
input: {},
expected: { strategy: "exact", value: "hello" },
},
"hello"
);
assert.equal(result.passed, true);
});
it("should fail exact match on mismatch", () => {
const result = evaluateCase(
{
id: "t2",
name: "test",
model: "test",
input: {},
expected: { strategy: "exact", value: "hello" },
},
"world"
);
assert.equal(result.passed, false);
});
it("should evaluate contains (case-insensitive)", () => {
const result = evaluateCase(
{
id: "t3",
name: "test",
model: "test",
input: {},
expected: { strategy: "contains", value: "paris" },
},
"The capital is Paris."
);
assert.equal(result.passed, true);
});
it("should evaluate regex", () => {
const result = evaluateCase(
{
id: "t4",
name: "test",
model: "test",
input: {},
expected: { strategy: "regex", value: "\\d+" },
},
"The answer is 42."
);
assert.equal(result.passed, true);
});
it("should evaluate custom function", () => {
const result = evaluateCase(
{
id: "t5",
name: "test",
model: "test",
input: {},
expected: { strategy: "custom", fn: (output) => output.length > 5 },
},
"this is long enough"
);
assert.equal(result.passed, true);
});
it("should handle unknown strategy gracefully", () => {
const result = evaluateCase(
{ id: "t6", name: "test", model: "test", input: {}, expected: { strategy: "unknown" } },
"test"
);
assert.equal(result.passed, false);
assert.ok(result.error.includes("Unknown strategy"));
});
it("should run suite and produce summary", () => {
registerSuite({
id: "test-suite",
name: "Test Suite",
cases: [
{
id: "c1",
name: "pass",
model: "m",
input: {},
expected: { strategy: "contains", value: "yes" },
},
{
id: "c2",
name: "fail",
model: "m",
input: {},
expected: { strategy: "contains", value: "no" },
},
],
});
const result = runSuite("test-suite", { c1: "yes it works", c2: "yes it works" });
assert.equal(result.summary.total, 2);
assert.equal(result.summary.passed, 1);
assert.equal(result.summary.failed, 1);
assert.equal(result.summary.passRate, 50);
});
it("should create scorecard from runs", () => {
const run1 = runSuite("test-suite", { c1: "yes", c2: "no" });
const scorecard = createScorecard([run1]);
assert.equal(scorecard.suites, 1);
assert.equal(scorecard.totalCases, 2);
});
it("should throw on unknown suite", () => {
assert.throws(() => runSuite("nonexistent", {}), { message: /not found/ });
});
});
// ──────────────── T-35: a11y Audit ────────────────
import { auditHTML, generateReport, WCAG_RULES } from "../../src/shared/utils/a11yAudit.ts";
describe("a11yAudit", () => {
it("should pass for compliant HTML", () => {
const html = '<button aria-label="Close">X</button><img alt="Logo" src="logo.png" />';
const violations = auditHTML(html);
const noImgViolations = violations.filter((v) => v.id !== WCAG_RULES.ARIA_LABEL);
assert.equal(noImgViolations.length, 0);
});
it("should detect images without alt text", () => {
const html = '<img src="photo.jpg" />';
const violations = auditHTML(html);
assert.ok(violations.some((v) => v.id === WCAG_RULES.IMAGE_ALT));
});
it("should detect dialogs without role", () => {
const html = '<div class="modal"><p>Content</p></div>';
const violations = auditHTML(html);
assert.ok(violations.some((v) => v.id === WCAG_RULES.DIALOG_ROLE));
});
it("should generate report summary", () => {
const violations = [
{ id: "test", description: "test", impact: "critical", help: "fix", nodes: [] },
{ id: "test2", description: "test", impact: "serious", help: "fix", nodes: [] },
];
const report = generateReport(violations);
assert.equal(report.total, 2);
assert.equal(report.critical, 1);
assert.equal(report.serious, 1);
assert.equal(report.passed, false);
});
it("should report passed for no violations", () => {
const report = generateReport([]);
assert.equal(report.passed, true);
assert.equal(report.total, 0);
});
it("should export WCAG rules", () => {
assert.ok(WCAG_RULES.ARIA_LABEL);
assert.ok(WCAG_RULES.COLOR_CONTRAST);
assert.ok(WCAG_RULES.FOCUS_TRAP);
});
});
// ──────────────── T-39: Responsive Specs ────────────────
import {
VIEWPORTS,
PAGES,
generateTestMatrix,
getViewportNames,
} from "../../tests/e2e/responsiveSpecs.mjs";
describe("responsiveSpecs", () => {
it("should define mobile, tablet, desktop viewports", () => {
assert.ok(VIEWPORTS.mobile);
assert.ok(VIEWPORTS.tablet);
assert.ok(VIEWPORTS.desktop);
assert.equal(VIEWPORTS.mobile.width, 375);
assert.equal(VIEWPORTS.tablet.width, 768);
});
it("should define pages to test", () => {
assert.ok(PAGES.length >= 4);
assert.ok(PAGES.some((p) => p.path === "/login"));
assert.ok(PAGES.some((p) => p.path === "/dashboard"));
});
it("should generate test matrix", () => {
const matrix = generateTestMatrix();
assert.equal(matrix.length, 3 * PAGES.length); // 3 viewports × n pages
assert.ok(matrix[0].testName);
assert.ok(matrix[0].viewport);
assert.ok(matrix[0].page);
});
it("should get viewport names", () => {
const names = getViewportNames();
assert.deepEqual(names, ["mobile", "tablet", "desktop"]);
});
});
// ──────────────── T-43: Compliance (noLog) ────────────────
import { setNoLog, isNoLog, getRetentionDays } from "../../src/lib/compliance/index.ts";
describe("compliance", () => {
it("should default to logging enabled", () => {
assert.equal(isNoLog("key-1"), false);
});
it("should set noLog opt-out", () => {
setNoLog("key-1", true);
assert.equal(isNoLog("key-1"), true);
});
it("should clear noLog opt-out", () => {
setNoLog("key-1", false);
assert.equal(isNoLog("key-1"), false);
});
it("should expose split default retention windows", () => {
assert.deepEqual(getRetentionDays(), { app: 7, call: 7 });
});
});