mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, e a suíte vitest:ui completa (2149) verde. Sobre esta PR especificamente: rodei os **23 arquivos de teste** que ela toca sobre o tip final, depois do merge da base — **392/392**. A migration `174_server_tool_executions.sql` não colide (o tip está em 173, e você já a renumerou em `c35f0fd7`). O dono foi consultado antes do merge, porque o loop está atrás da flag `SERVER_OWNED_TOOL_LOOP_ENABLED` mas o primeiro send não-streaming mudou de dono sem flag, e a verificação manual em combo com Memory continuava desmarcada. A condição dele foi: entra se os testes focados passarem aqui. Passaram. O lock de passthrough (`fetchCalls.length === 1`) é a parte que mais me convenceu — o double-dispatch que um `if (stream)` em volta do send existente causaria é exatamente o tipo de regressão que não aparece em teste de comportamento, só em contagem de chamada. **Três ajustes meus na sua branch:** 1. `tests/unit/chatcore-stream-error-result.test.ts` procurava `"const legResult = await runNonStreamingProviderLeg"`, mas o seu commit final `6077b9dd` passou a reatribuir `legResult` e trocou para `let`. O guard falhava na sua própria branch (confirmei que o arquivo e o `chatCore.ts` eram byte-idênticos ao head da PR, então não era efeito da leva). Passou a aceitar `const|let` — a intenção do guard é o try/catch em volta da chamada, não a palavra-chave. 2. `tests/integration/skills-pipeline.test.ts` foi de 1156 para 1338 linhas e estourou o `testCap` de 1200. Segui o mesmo caminho que você já tinha tomado em `a1d2d20d` para os testes unitários: extraí os três casos do server-owned tool loop para `tests/integration/server-owned-tool-loop-pipeline.test.ts` (259 linhas), com instância própria do harness. O glob `tests/integration/*.test.ts` pega o arquivo novo sem registro adicional. 3/3 verdes isolados. 3. O arquivo novo herdou cinco `any` do original — que só passavam por estarem congelados no `eslint-suppressions.json` sob o nome antigo. Tipei como `Record<string, unknown>`. E `tests/unit/non-streaming-finalization.test.ts` tinha dois argumentos não usados em `trackPendingRequest`, agora prefixados com `_`. Nada disso toca produção nem enfraquece asserção.
243 lines
8.6 KiB
TypeScript
243 lines
8.6 KiB
TypeScript
process.env.DISABLE_SQLITE_AUTO_BACKUP = "true";
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
import { createRequire } from "node:module";
|
|
|
|
const require_ = createRequire(import.meta.url);
|
|
const BetterSqlite3 = require_("better-sqlite3") as typeof import("better-sqlite3");
|
|
|
|
import { createBetterSqliteAdapter } from "../../src/lib/db/adapters/betterSqliteAdapter";
|
|
import type { SqliteAdapter } from "../../src/lib/db/adapters/types";
|
|
|
|
const MIGRATION_174_PATH = path.resolve(
|
|
import.meta.dirname ?? ".",
|
|
"../../src/lib/db/migrations/174_server_tool_executions.sql"
|
|
);
|
|
const MIGRATION_174_SQL = fs.readFileSync(MIGRATION_174_PATH, "utf8");
|
|
|
|
// Minimal pre-174 fixture: only skills + skill_executions tables.
|
|
// No SCHEMA_SQL import from core.ts, no runMigrations.
|
|
const PRE_174_FIXTURE = `
|
|
CREATE TABLE IF NOT EXISTS skills (
|
|
id TEXT PRIMARY KEY,
|
|
api_key_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
version TEXT NOT NULL DEFAULT '1.0.0',
|
|
description TEXT,
|
|
schema TEXT NOT NULL,
|
|
handler TEXT NOT NULL,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE TABLE IF NOT EXISTS skill_executions (
|
|
id TEXT PRIMARY KEY,
|
|
skill_id TEXT NOT NULL,
|
|
api_key_id TEXT NOT NULL,
|
|
session_id TEXT,
|
|
input TEXT NOT NULL,
|
|
output TEXT,
|
|
status TEXT NOT NULL CHECK(status IN ('pending', 'running', 'success', 'error', 'timeout')),
|
|
error_message TEXT,
|
|
duration_ms INTEGER,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_skill_executions_skill ON skill_executions(skill_id);
|
|
CREATE INDEX IF NOT EXISTS idx_skill_executions_api_key ON skill_executions(api_key_id);
|
|
`;
|
|
|
|
function makeTempDb(): {
|
|
adapter: SqliteAdapter;
|
|
dir: string;
|
|
raw: import("better-sqlite3").Database;
|
|
} {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "migration-174-test-"));
|
|
const dbPath = path.join(dir, "test.db");
|
|
const raw = new BetterSqlite3(dbPath);
|
|
raw.pragma("journal_mode = WAL");
|
|
raw.pragma("busy_timeout = 2000");
|
|
// Create migrations tracking table
|
|
raw.exec(`
|
|
CREATE TABLE IF NOT EXISTS _omniroute_migrations (
|
|
version TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
`);
|
|
// Apply minimal pre-174 fixture (skills + skill_executions only)
|
|
raw.exec(PRE_174_FIXTURE);
|
|
// Apply migration 174 via raw.exec (real SQL, twice for idempotency)
|
|
raw.exec(MIGRATION_174_SQL);
|
|
raw.exec(MIGRATION_174_SQL);
|
|
const adapter = createBetterSqliteAdapter(raw);
|
|
return { adapter, dir, raw };
|
|
}
|
|
|
|
function getColumnInfo(raw: import("better-sqlite3").Database, table: string) {
|
|
return raw.pragma(`table_info(${table})`) as Array<{
|
|
name: string;
|
|
type: string;
|
|
notnull: number;
|
|
pk: number;
|
|
}>;
|
|
}
|
|
|
|
function getIndexInfo(raw: import("better-sqlite3").Database, table: string) {
|
|
return raw.pragma(`index_list(${table})`) as Array<{
|
|
name: string;
|
|
unique: number;
|
|
}>;
|
|
}
|
|
|
|
// ── Migration 174 tests ──
|
|
|
|
test("migration 174: server_tool_executions table exists with correct columns", (_, done) => {
|
|
const { raw, dir } = makeTempDb();
|
|
try {
|
|
const tables = raw.pragma("table_list") as Array<{ name: string }>;
|
|
const names = tables.map((t) => t.name);
|
|
assert.ok(
|
|
names.includes("server_tool_executions"),
|
|
`Expected server_tool_executions in: ${names.join(", ")}`
|
|
);
|
|
const cols = getColumnInfo(raw, "server_tool_executions");
|
|
const colNames = cols.map((c) => c.name);
|
|
assert.ok(colNames.includes("id"), "must have id column");
|
|
assert.ok(colNames.includes("api_key_id"), "must have api_key_id column");
|
|
assert.ok(colNames.includes("request_identity"), "must have request_identity column");
|
|
assert.ok(colNames.includes("tool_call_id"), "must have tool_call_id column");
|
|
assert.ok(colNames.includes("tool_name"), "must have tool_name column");
|
|
assert.ok(colNames.includes("input_digest"), "must have input_digest column");
|
|
assert.ok(colNames.includes("status"), "must have status column");
|
|
assert.ok(colNames.includes("claim_expires_at"), "must have claim_expires_at column");
|
|
assert.ok(colNames.includes("duration_ms"), "must have duration_ms column");
|
|
} finally {
|
|
raw.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
done();
|
|
}
|
|
});
|
|
|
|
test("migration 174: UNIQUE constraint on (api_key_id, request_identity, tool_call_id)", (_, done) => {
|
|
const { raw, dir } = makeTempDb();
|
|
try {
|
|
raw.exec(`
|
|
INSERT INTO server_tool_executions
|
|
(id, api_key_id, request_identity, tool_call_id, tool_name, input_digest, status, claim_expires_at)
|
|
VALUES ('e1','k1','r1','c1','tool_a','d1','running',datetime('now'))
|
|
`);
|
|
assert.throws(() => {
|
|
raw.exec(`
|
|
INSERT INTO server_tool_executions
|
|
(id, api_key_id, request_identity, tool_call_id, tool_name, input_digest, status, claim_expires_at)
|
|
VALUES ('e2','k1','r1','c1','tool_a','d1','running',datetime('now'))
|
|
`);
|
|
}, /UNIQUE/i);
|
|
} finally {
|
|
raw.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
done();
|
|
}
|
|
});
|
|
|
|
test("migration 174: two indexes exist on server_tool_executions", (_, done) => {
|
|
const { raw, dir } = makeTempDb();
|
|
try {
|
|
const indexes = getIndexInfo(raw, "server_tool_executions");
|
|
const names = indexes.map((i) => i.name);
|
|
assert.ok(
|
|
names.some((n) => n.includes("status_expiry")),
|
|
`Expected status_expiry index, got: ${names.join(", ")}`
|
|
);
|
|
assert.ok(
|
|
names.some((n) => n.includes("created")),
|
|
`Expected created index, got: ${names.join(", ")}`
|
|
);
|
|
} finally {
|
|
raw.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
done();
|
|
}
|
|
});
|
|
|
|
test("migration 174: existing skill_executions data preserved after migration", (_, done) => {
|
|
const { raw, dir } = makeTempDb();
|
|
try {
|
|
// Insert a skill to satisfy FK
|
|
raw.exec(`
|
|
INSERT INTO skills (id, api_key_id, name, version, schema, handler)
|
|
VALUES ('s1','k1','test','1.0.0','{}','h.js')
|
|
`);
|
|
raw.exec(`
|
|
INSERT INTO skill_executions (id, skill_id, api_key_id, input, status)
|
|
VALUES ('old_exec','s1','k1','{"q":"test"}','success')
|
|
`);
|
|
const rows = raw.prepare("SELECT * FROM skill_executions WHERE id = 'old_exec'").all();
|
|
assert.equal(rows.length, 1, "old row should exist after migration 174");
|
|
} finally {
|
|
raw.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
done();
|
|
}
|
|
});
|
|
|
|
test("migration 174: skill_executions still enforces skill_id NOT NULL", (_, done) => {
|
|
const { raw, dir } = makeTempDb();
|
|
try {
|
|
assert.throws(() => {
|
|
raw.exec(
|
|
`INSERT INTO skill_executions (id, api_key_id, input, status)
|
|
VALUES ('bad','k1','{}','running')`
|
|
);
|
|
}, /NOT NULL/i);
|
|
} finally {
|
|
raw.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
done();
|
|
}
|
|
});
|
|
|
|
test("migration 174: custom skill execution write still works after migration", (_, done) => {
|
|
const { raw, dir } = makeTempDb();
|
|
try {
|
|
// Insert a skill to satisfy FK
|
|
raw.exec(`
|
|
INSERT INTO skills (id, api_key_id, name, version, schema, handler)
|
|
VALUES ('s2','k1','test2','1.0.0','{}','h.js')
|
|
`);
|
|
raw.exec(`
|
|
INSERT INTO skill_executions (id, skill_id, api_key_id, input, status)
|
|
VALUES ('new_exec','s2','k1','{"q":"test2"}','success')
|
|
`);
|
|
const allRows = raw.prepare("SELECT * FROM skill_executions").all();
|
|
assert.ok(allRows.length >= 1, "should read skill_executions after migration 174");
|
|
} finally {
|
|
raw.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
done();
|
|
}
|
|
});
|
|
|
|
test("migration 174: idempotent — running twice does not error or duplicate", (_, done) => {
|
|
const { raw, dir } = makeTempDb();
|
|
try {
|
|
// makeTempDb already runs the SQL twice; verify no error and table exists
|
|
const tables = raw.pragma("table_list") as Array<{ name: string }>;
|
|
const names = tables.map((t) => t.name);
|
|
assert.ok(names.includes("server_tool_executions"), "table must exist after double-apply");
|
|
// Verify no duplicate columns or indexes from double-apply
|
|
const indexes = getIndexInfo(raw, "server_tool_executions");
|
|
const statusIdx = indexes.filter((i) => i.name.includes("status_expiry"));
|
|
assert.equal(statusIdx.length, 1, "must have exactly one status_expiry index");
|
|
} finally {
|
|
raw.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
done();
|
|
}
|
|
});
|