Заведи каркас пакета и утилитный слой MCP-сервера
package.json, tsconfig, eslint, генерация типов из OpenAPI-спеки, модули config/redact/errors/money/sanitize/cache с тестами и обезличенные фикстуры живых ответов API openrusrouter.ru. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
29 files changed
+11350
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { redactError, redactString } from "../src/redact.js";
|
||||
import { ApiError, toToolErrorResult } from "../src/errors.js";
|
||||
|
||||
const KEY = "orr_live_AbCdEf0123456789GhIjKlMnOpQrStUv";
|
||||
|
||||
function assertClean(serialized: string): void {
|
||||
assert.ok(!serialized.includes(KEY), serialized);
|
||||
assert.ok(!serialized.includes(`Bearer ${KEY}`), serialized);
|
||||
assert.ok(!serialized.includes(KEY.slice(9)), serialized);
|
||||
}
|
||||
|
||||
test("redactString маскирует Bearer и ключи", () => {
|
||||
const out = redactString(`Authorization: Bearer ${KEY}; ключ=${KEY}; x${KEY}`);
|
||||
assertClean(out);
|
||||
assert.match(out, /Bearer \[REDACTED\]/);
|
||||
});
|
||||
|
||||
test("ключ в message", () => {
|
||||
const e = new Error(`request failed for ${KEY}`);
|
||||
assertClean(JSON.stringify(redactError(e)));
|
||||
});
|
||||
|
||||
test("ключ в cause.message не протекает", () => {
|
||||
const e = new Error("outer", { cause: new Error(`inner Bearer ${KEY}`) });
|
||||
const r = redactError(e);
|
||||
assertClean(JSON.stringify(r));
|
||||
assert.equal(r.message, "outer");
|
||||
});
|
||||
|
||||
test("headers/config/request/stack не копируются", () => {
|
||||
const e = Object.assign(new Error("boom"), {
|
||||
status: 401,
|
||||
code: "invalid_key",
|
||||
path: "/api/balance",
|
||||
headers: { Authorization: `Bearer ${KEY}` },
|
||||
config: { headers: { Authorization: `Bearer ${KEY}` } },
|
||||
request: { auth: KEY },
|
||||
});
|
||||
e.stack = `Error: ${KEY}\n at x`;
|
||||
const r = redactError(e);
|
||||
assertClean(JSON.stringify(r));
|
||||
assert.deepEqual(Object.keys(r).sort(), ["code", "message", "name", "path", "status"]);
|
||||
assert.equal(r.status, 401);
|
||||
});
|
||||
|
||||
test("произвольные значения", () => {
|
||||
assertClean(JSON.stringify(redactError(`string ${KEY}`)));
|
||||
assertClean(JSON.stringify(redactError({ message: `obj ${KEY}`, headers: { a: KEY } })));
|
||||
assert.equal(redactError(null).message, "неизвестная ошибка");
|
||||
assert.equal(redactError(42).message, "неизвестная ошибка");
|
||||
});
|
||||
|
||||
test("toToolErrorResult маппит статусы и редактирует текст", () => {
|
||||
const cases: Array<[number, RegExp]> = [
|
||||
[401, /недействителен или отозван/],
|
||||
[403, /запрещено/],
|
||||
[429, /лимит частоты/],
|
||||
[503, /временно недоступен/],
|
||||
];
|
||||
for (const [status, re] of cases) {
|
||||
const r = toToolErrorResult(new ApiError(status, "/api/balance", `oops ${KEY}`));
|
||||
assert.equal(r.isError, true);
|
||||
assert.match(r.content[0].text, re);
|
||||
assertClean(JSON.stringify(r));
|
||||
}
|
||||
const other = toToolErrorResult(new ApiError(400, "/api/usage", `bad Bearer ${KEY}`));
|
||||
assert.match(other.content[0].text, /bad Bearer \[REDACTED\]/);
|
||||
assertClean(JSON.stringify(other));
|
||||
assertClean(JSON.stringify(toToolErrorResult(new Error(`plain ${KEY}`))));
|
||||
});
|
||||
Reference in new issue
Block a user