mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-22 23:22:09 +03:00
⭐5 — Cursor PKCE login com Bearer quota, auto router e empty-turn errors. Feature completa e testada (11 arquivos de teste, 133 testes focados, todos verdes).
**Validação (worktree combinado `.claude/worktrees/fix-9909`, board sobre `origin/release/v3.8.50`):**
- 3 conflitos reais resolvidos: `config/quality/eslint-suppressions.json` (aditivo), `open-sse/config/providers/registry/cursor/index.ts` (dedup de 208 entradas de catálogo, 0 IDs duplicados verificado), `open-sse/executors/cursor.ts` (imports aditivos).
- `npm run typecheck:core`: limpo.
- `check-changelog-integrity`, `check-file-size`, `check-complexity` (2615/2774), `check-cognitive-complexity` (1175/1223), `check-dead-code` (410/416): todos OK.
- `check-public-creds`: 1 entrada obsoleta pré-existente na allowlist (`copilot-m365-web.ts:330`), já presente no tip da release — não é desta PR.
- `npm run lint`: 0 errors (5 warnings pré-existentes).
- Testes focados (`cursor-agent-cli-version`, `cursor-available-models`, `cursor-catalog-combo-compat`, `cursor-errors-classify`, `cursor-login-pkce`, `cursor-model-effort-suffix-7289`, `cursor-streaming`, `cursor-token-extractor`, `cursor-token-refresh-wiring`, `cursor-usage-fetcher`, `empty-stream-no-content-8649`): 133/133 verdes.
- Corrigido durante a validação: 1 teste novo da própria PR (`cursor-model-effort-suffix-7289.test.ts`, "splits effort off legacy grok- ids") colidia com `CURSOR_MODEL_ALIASES` já mesclado na release (mapeia `grok-4.5-high` → `cursor-grok-4.5-high` antes do fallback legado rodar); ajustado para usar um id não-aliasado (`grok-3-high`) que de fato exercita o fallback — commit `68b58ed`.
Obrigado pela contribuição, @yansigit — feature robusta com boa cobertura de testes.
79 lines
3.1 KiB
TypeScript
79 lines
3.1 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
classifyCursorError,
|
|
classifyCursorErrorKind,
|
|
isCursorBenignCancelError,
|
|
isCursorRequestTooLargeDetail,
|
|
resolveCursorEmptyTurnError,
|
|
CURSOR_EMPTY_TURN_MESSAGE,
|
|
} from "../../open-sse/executors/cursor/cursorErrors.ts";
|
|
|
|
test("classifyCursorErrorKind: resource_exhausted without size cue is rate_limit", () => {
|
|
assert.equal(classifyCursorErrorKind("resource_exhausted: quota exhausted"), "rate_limit");
|
|
assert.equal(classifyCursorErrorKind("You're out of usage. Increase limits"), "rate_limit");
|
|
});
|
|
|
|
test("classifyCursorErrorKind: resource_exhausted + size cue is invalid", () => {
|
|
assert.equal(classifyCursorErrorKind("resource_exhausted: tool catalog too large"), "invalid");
|
|
assert.equal(isCursorRequestTooLargeDetail("tool catalog too large"), true);
|
|
assert.equal(
|
|
isCursorRequestTooLargeDetail("resource_exhausted while loading tool catalog: quota exhausted"),
|
|
false
|
|
);
|
|
});
|
|
|
|
test("classifyCursorErrorKind: auth cues", () => {
|
|
assert.equal(classifyCursorErrorKind("Authentication error"), "auth");
|
|
assert.equal(classifyCursorErrorKind("unauthenticated"), "auth");
|
|
});
|
|
|
|
test("classifyCursorErrorKind: AI Model Not Found is rate_limit (Cursor out-of-usage)", () => {
|
|
assert.equal(
|
|
classifyCursorErrorKind("not_found: AI Model Not Found (reset after 109h 9s)"),
|
|
"rate_limit"
|
|
);
|
|
assert.equal(classifyCursorErrorKind("not_found: AI Model Not Found"), "rate_limit");
|
|
assert.equal(classifyCursorErrorKind("model not found"), "invalid");
|
|
});
|
|
|
|
test("classifyCursorError redacts credentials and paths", () => {
|
|
const out = classifyCursorError(
|
|
"Authentication failed authorization=secret-token path=/Users/alice/code/file"
|
|
);
|
|
assert.equal(out.kind, "auth");
|
|
assert.equal(out.status, 401);
|
|
assert.match(out.message, /\[REDACTED\]/);
|
|
assert.match(out.message, /\[REDACTED_PATH\]/);
|
|
assert.doesNotMatch(out.message, /secret-token/);
|
|
assert.doesNotMatch(out.message, /\/Users\/alice/);
|
|
});
|
|
|
|
test("isCursorBenignCancelError recognizes NGHTTP2_CANCEL / suspend", () => {
|
|
assert.equal(isCursorBenignCancelError({ code: "NGHTTP2_CANCEL" }), true);
|
|
assert.equal(isCursorBenignCancelError(new Error("nghttp2_cancel")), true);
|
|
assert.equal(isCursorBenignCancelError(new Error("Cursor stream suspended")), true);
|
|
assert.equal(isCursorBenignCancelError(new Error("resource_exhausted")), false);
|
|
});
|
|
|
|
test("resolveCursorEmptyTurnError: no upstream → actionable 502", () => {
|
|
const out = resolveCursorEmptyTurnError({});
|
|
assert.equal(out.status, 502);
|
|
assert.equal(out.message, CURSOR_EMPTY_TURN_MESSAGE);
|
|
});
|
|
|
|
test("resolveCursorEmptyTurnError: quotaExhaustedHint → 429", () => {
|
|
const out = resolveCursorEmptyTurnError({ quotaExhaustedHint: true });
|
|
assert.equal(out.status, 429);
|
|
assert.equal(out.type, "rate_limit_error");
|
|
assert.equal(out.message, CURSOR_EMPTY_TURN_MESSAGE);
|
|
});
|
|
|
|
test("resolveCursorEmptyTurnError: classifies upstream quota message as 429", () => {
|
|
const out = resolveCursorEmptyTurnError({
|
|
upstreamMessage: "resource_exhausted: too many requests",
|
|
});
|
|
assert.equal(out.status, 429);
|
|
assert.equal(out.kind, "rate_limit");
|
|
});
|