652 lines
20 KiB
TypeScript
652 lines
20 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import net from "node:net";
|
|
|
|
const isWindows = process.platform === "win32";
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-fixes-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const backupDb = await import("../../src/lib/db/backup.ts");
|
|
const providersDb = await import("../../src/lib/db/providers.ts");
|
|
const combosDb = await import("../../src/lib/db/combos.ts");
|
|
const settingsDb = await import("../../src/lib/db/settings.ts");
|
|
const localDb = await import("../../src/lib/localDb.ts");
|
|
const tokenRefresh = await import("../../open-sse/services/tokenRefresh.ts");
|
|
const proxyFetch = await import("../../open-sse/utils/proxyFetch.ts");
|
|
const proxyDispatcher = await import("../../open-sse/utils/proxyDispatcher.ts");
|
|
const proxySettingsRoute = await import("../../src/app/api/settings/proxy/route.ts");
|
|
const proxyTestRoute = await import("../../src/app/api/settings/proxy/test/route.ts");
|
|
const shutdownRoute = await import("../../src/app/api/shutdown/route.ts");
|
|
const restartRoute = await import("../../src/app/api/restart/route.ts");
|
|
|
|
async function withEnv(name, value, fn) {
|
|
const previous = process.env[name];
|
|
if (value === undefined) {
|
|
delete process.env[name];
|
|
} else {
|
|
process.env[name] = value;
|
|
}
|
|
|
|
try {
|
|
return await fn();
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env[name];
|
|
} else {
|
|
process.env[name] = previous;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function resetStorage() {
|
|
core.resetDbInstance();
|
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
try {
|
|
if (fs.existsSync(TEST_DATA_DIR)) {
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
}
|
|
break;
|
|
} catch (err: any) {
|
|
if ((err?.code === "EBUSY" || err?.code === "EPERM") && attempt < 9) {
|
|
await new Promise((r) => setTimeout(r, 100 * (attempt + 1)));
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
test.after(async () => {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
test("token refresh dedupe key avoids collision for same-prefix tokens", async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const requests = [];
|
|
|
|
globalThis.fetch = async (_url, options = {}) => {
|
|
const refreshToken = options.body?.get?.("refresh_token") || "unknown";
|
|
requests.push(refreshToken);
|
|
return new Response(
|
|
JSON.stringify({
|
|
access_token: `access-${refreshToken}`,
|
|
refresh_token: `refresh-${refreshToken}`,
|
|
expires_in: 3600,
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}
|
|
);
|
|
};
|
|
|
|
try {
|
|
const prefix = "abcdefghijklmnop";
|
|
const tokenA = `${prefix}-account-a`;
|
|
const tokenB = `${prefix}-account-b`;
|
|
|
|
const [resultA, resultB] = await Promise.all([
|
|
tokenRefresh.getAccessToken("codex", { refreshToken: tokenA }, null),
|
|
tokenRefresh.getAccessToken("codex", { refreshToken: tokenB }, null),
|
|
]);
|
|
|
|
assert.equal(requests.length, 2);
|
|
assert.equal(resultA.accessToken, `access-${tokenA}`);
|
|
assert.equal(resultB.accessToken, `access-${tokenB}`);
|
|
assert.notEqual(resultA.accessToken, resultB.accessToken);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test(
|
|
"restoreDbBackup clears stale sqlite sidecars before reopen",
|
|
{ skip: isWindows },
|
|
async () => {
|
|
await resetStorage();
|
|
|
|
const db = core.getDbInstance();
|
|
const now = new Date().toISOString();
|
|
db.prepare(
|
|
"INSERT INTO provider_connections (id, provider, auth_type, name, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
).run("restore-test-conn", "openai", "apikey", "restore-test", 1, now, now);
|
|
|
|
const backupId = "db_2000-01-01T00-00-00-000Z_manual.sqlite";
|
|
const backupPath = path.join(core.DB_BACKUPS_DIR, backupId);
|
|
fs.mkdirSync(core.DB_BACKUPS_DIR, { recursive: true });
|
|
await db.backup(backupPath);
|
|
|
|
core.resetDbInstance();
|
|
fs.writeFileSync(`${core.SQLITE_FILE}-wal`, "STALE-WAL-MARKER");
|
|
fs.writeFileSync(`${core.SQLITE_FILE}-shm`, "STALE-SHM-MARKER");
|
|
fs.writeFileSync(`${core.SQLITE_FILE}-journal`, "STALE-JOURNAL-MARKER");
|
|
|
|
await backupDb.restoreDbBackup(backupId);
|
|
|
|
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
const sidecarPath = `${core.SQLITE_FILE}${suffix}`;
|
|
if (!fs.existsSync(sidecarPath)) continue;
|
|
const text = fs.readFileSync(sidecarPath, "utf8");
|
|
assert.equal(text.includes("STALE-"), false, `sidecar ${suffix} still contains stale marker`);
|
|
}
|
|
|
|
const reopenedDb = core.getDbInstance();
|
|
const row = reopenedDb
|
|
.prepare("SELECT COUNT(*) AS cnt FROM provider_connections WHERE id = ?")
|
|
.get("restore-test-conn");
|
|
assert.equal((row as any).cnt, 1);
|
|
}
|
|
);
|
|
|
|
test("closeDbInstance checkpoints WAL changes into the primary SQLite file", async () => {
|
|
await resetStorage();
|
|
|
|
const db = core.getDbInstance();
|
|
const now = new Date().toISOString();
|
|
db.prepare(
|
|
"INSERT INTO provider_connections (id, provider, auth_type, name, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
).run("checkpoint-test-conn", "openai", "apikey", "checkpoint-test", 1, now, now);
|
|
|
|
core.closeDbInstance();
|
|
|
|
const snapshotPath = path.join(TEST_DATA_DIR, "storage-snapshot.sqlite");
|
|
fs.copyFileSync(core.SQLITE_FILE, snapshotPath);
|
|
|
|
const Database = (await import("better-sqlite3")).default;
|
|
const snapshotDb = new Database(snapshotPath, { readonly: true });
|
|
try {
|
|
const row = snapshotDb
|
|
.prepare("SELECT name FROM provider_connections WHERE id = ?")
|
|
.get("checkpoint-test-conn");
|
|
(assert as any).equal((row as any).name, "checkpoint-test");
|
|
} finally {
|
|
snapshotDb.close();
|
|
}
|
|
});
|
|
|
|
test("shutdown route uses SIGTERM for graceful shutdown", async () => {
|
|
const originalKill = process.kill;
|
|
const originalSetTimeout = globalThis.setTimeout;
|
|
const calls = [];
|
|
|
|
process.kill = (pid, signal) => {
|
|
calls.push({ pid, signal });
|
|
return true;
|
|
};
|
|
globalThis.setTimeout = (callback) => {
|
|
callback();
|
|
return 0;
|
|
};
|
|
|
|
try {
|
|
const response = await shutdownRoute.POST();
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(calls, [{ pid: process.pid, signal: "SIGTERM" }]);
|
|
} finally {
|
|
process.kill = originalKill;
|
|
globalThis.setTimeout = originalSetTimeout;
|
|
}
|
|
});
|
|
|
|
test("restart route uses SIGTERM for graceful restart", async () => {
|
|
const originalKill = process.kill;
|
|
const originalSetTimeout = globalThis.setTimeout;
|
|
const calls = [];
|
|
|
|
process.kill = (pid, signal) => {
|
|
calls.push({ pid, signal });
|
|
return true;
|
|
};
|
|
globalThis.setTimeout = (callback) => {
|
|
callback();
|
|
return 0;
|
|
};
|
|
|
|
try {
|
|
const response = await restartRoute.POST();
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(calls, [{ pid: process.pid, signal: "SIGTERM" }]);
|
|
} finally {
|
|
process.kill = originalKill;
|
|
globalThis.setTimeout = originalSetTimeout;
|
|
}
|
|
});
|
|
|
|
test("unlinkFileWithRetry retries EBUSY/EPERM and eventually succeeds", async () => {
|
|
const target = path.join(TEST_DATA_DIR, "retry-target.tmp");
|
|
fs.writeFileSync(target, "retry-me");
|
|
|
|
const originalExistsSync = fs.existsSync;
|
|
const originalUnlinkSync = fs.unlinkSync;
|
|
const seenCodes = [];
|
|
let attempts = 0;
|
|
|
|
fs.existsSync = (filePath) => {
|
|
if (filePath === target) return attempts < 3 || originalExistsSync(filePath);
|
|
return originalExistsSync(filePath);
|
|
};
|
|
|
|
fs.unlinkSync = (filePath) => {
|
|
if (filePath === target) {
|
|
attempts++;
|
|
if (attempts === 1) {
|
|
const err = new Error("busy");
|
|
err.code = "EBUSY";
|
|
seenCodes.push(err.code);
|
|
throw err;
|
|
}
|
|
if (attempts === 2) {
|
|
const err = new Error("perm");
|
|
err.code = "EPERM";
|
|
seenCodes.push(err.code);
|
|
throw err;
|
|
}
|
|
}
|
|
return originalUnlinkSync(filePath);
|
|
};
|
|
|
|
try {
|
|
await backupDb.unlinkFileWithRetry(target, { maxAttempts: 5, baseDelayMs: 1 });
|
|
assert.equal(attempts, 3);
|
|
assert.deepEqual(seenCodes, ["EBUSY", "EPERM"]);
|
|
assert.equal(fs.existsSync(target), false);
|
|
} finally {
|
|
fs.existsSync = originalExistsSync;
|
|
fs.unlinkSync = originalUnlinkSync;
|
|
if (originalExistsSync(target)) originalUnlinkSync(target);
|
|
}
|
|
});
|
|
|
|
test("provider connection persists rateLimitProtection across reopen", async () => {
|
|
await resetStorage();
|
|
|
|
const created = await providersDb.createProviderConnection({
|
|
provider: "openai",
|
|
authType: "apikey",
|
|
name: "rl-test",
|
|
apiKey: "sk-test",
|
|
});
|
|
|
|
await providersDb.updateProviderConnection((created as any).id, { rateLimitProtection: true });
|
|
|
|
const firstRead = await providersDb.getProviderConnectionById((created as any).id);
|
|
assert.equal(firstRead.rateLimitProtection, true);
|
|
|
|
core.resetDbInstance();
|
|
const secondRead = await providersDb.getProviderConnectionById((created as any).id);
|
|
assert.equal(secondRead.rateLimitProtection, true);
|
|
});
|
|
|
|
test('provider connection migration adds "group" column for existing databases', async () => {
|
|
await resetStorage();
|
|
|
|
const sqlitePath = core.SQLITE_FILE;
|
|
core.resetDbInstance();
|
|
|
|
const Database = (await import("better-sqlite3")).default;
|
|
const db = new Database(sqlitePath);
|
|
db.exec(`
|
|
CREATE TABLE provider_connections (
|
|
id TEXT PRIMARY KEY,
|
|
provider TEXT NOT NULL,
|
|
auth_type TEXT,
|
|
name TEXT,
|
|
email TEXT,
|
|
priority INTEGER DEFAULT 0,
|
|
is_active INTEGER DEFAULT 1,
|
|
access_token TEXT,
|
|
refresh_token TEXT,
|
|
expires_at TEXT,
|
|
token_expires_at TEXT,
|
|
scope TEXT,
|
|
project_id TEXT,
|
|
test_status TEXT,
|
|
error_code TEXT,
|
|
last_error TEXT,
|
|
last_error_at TEXT,
|
|
last_error_type TEXT,
|
|
last_error_source TEXT,
|
|
backoff_level INTEGER DEFAULT 0,
|
|
rate_limited_until TEXT,
|
|
health_check_interval INTEGER,
|
|
last_health_check_at TEXT,
|
|
last_tested TEXT,
|
|
api_key TEXT,
|
|
id_token TEXT,
|
|
provider_specific_data TEXT,
|
|
expires_in INTEGER,
|
|
display_name TEXT,
|
|
global_priority INTEGER,
|
|
default_model TEXT,
|
|
token_type TEXT,
|
|
consecutive_use_count INTEGER DEFAULT 0,
|
|
rate_limit_protection INTEGER DEFAULT 0,
|
|
last_used_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
)
|
|
`);
|
|
db.close();
|
|
|
|
const reopened = core.getDbInstance();
|
|
const columns = reopened.prepare("PRAGMA table_info(provider_connections)").all();
|
|
const names = new Set(columns.map((column) => (column as any).name));
|
|
assert.equal(names.has("group"), true);
|
|
});
|
|
|
|
test("resolveProxyForConnection applies combo proxy for object/string model entries", async () => {
|
|
await resetStorage();
|
|
|
|
const conn = await providersDb.createProviderConnection({
|
|
provider: "claude",
|
|
authType: "oauth",
|
|
email: "combo-test@example.com",
|
|
accessToken: "access",
|
|
refreshToken: "refresh",
|
|
});
|
|
|
|
const combo = await combosDb.createCombo({
|
|
id: "combo-proxy-test",
|
|
name: "combo-proxy-test",
|
|
strategy: "priority",
|
|
models: ["openai/gpt-5", { model: "cc/claude-sonnet-4-5-20250929", weight: 100 }],
|
|
});
|
|
|
|
await settingsDb.setProxyForLevel("combo", (combo as any).id, {
|
|
type: "http",
|
|
host: "127.0.0.1",
|
|
port: "8080",
|
|
});
|
|
|
|
const resolved = await settingsDb.resolveProxyForConnection((conn as any).id);
|
|
assert.equal(resolved.level, "combo");
|
|
assert.equal(resolved.levelId, combo.id);
|
|
});
|
|
|
|
test("normalizeProxyUrl accepts socks5 only when explicitly allowed", () => {
|
|
const socksUrl = "socks5://127.0.0.1:1080";
|
|
|
|
const normalized = proxyDispatcher.normalizeProxyUrl(socksUrl, "test proxy", {
|
|
allowSocks5: true,
|
|
});
|
|
assert.match(normalized, /^socks5:\/\/127\.0\.0\.1:1080\/?$/);
|
|
|
|
assert.throws(
|
|
() =>
|
|
proxyDispatcher.normalizeProxyUrl(socksUrl, "test proxy", {
|
|
allowSocks5: false,
|
|
}),
|
|
/SOCKS5 proxy is disabled/i
|
|
);
|
|
});
|
|
|
|
test("createProxyDispatcher builds dispatchers for http, https, and socks5", async () => {
|
|
await withEnv("ENABLE_SOCKS5_PROXY", "true", async () => {
|
|
const httpDispatcher = proxyDispatcher.createProxyDispatcher("http://127.0.0.1:8080");
|
|
const httpsDispatcher = proxyDispatcher.createProxyDispatcher("https://127.0.0.1:8443");
|
|
const socksDispatcher = proxyDispatcher.createProxyDispatcher("socks5://127.0.0.1:1080");
|
|
|
|
assert.equal(typeof httpDispatcher.dispatch, "function");
|
|
assert.equal(typeof httpsDispatcher.dispatch, "function");
|
|
assert.equal(typeof socksDispatcher.dispatch, "function");
|
|
});
|
|
});
|
|
|
|
test("proxy fetch fails closed when context proxy is invalid", async () => {
|
|
await assert.rejects(
|
|
proxyFetch.runWithProxyContext({ type: "http", host: "127.0.0.1", port: "9" }, async () =>
|
|
fetch("https://example.com")
|
|
)
|
|
);
|
|
});
|
|
|
|
test("proxy fetch rejects socks5 context when feature flag is disabled", async () => {
|
|
await withEnv("ENABLE_SOCKS5_PROXY", "false", async () => {
|
|
await assert.rejects(
|
|
proxyFetch.runWithProxyContext(
|
|
{ type: "socks5", host: "127.0.0.1", port: "1080" },
|
|
async () => fetch("https://example.com")
|
|
),
|
|
/ENABLE_SOCKS5_PROXY/i
|
|
);
|
|
});
|
|
});
|
|
|
|
test("proxy fetch accepts socks5 context when feature flag is enabled", async () => {
|
|
await withEnv("ENABLE_SOCKS5_PROXY", "true", async () => {
|
|
const server = net.createServer();
|
|
await new Promise((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(0, "127.0.0.1", resolve);
|
|
});
|
|
|
|
const address = server.address();
|
|
assert.ok(address && typeof address === "object");
|
|
|
|
try {
|
|
const result = await proxyFetch.runWithProxyContext(
|
|
{ type: "socks5", host: "127.0.0.1", port: String(address.port) },
|
|
async () => "ok"
|
|
);
|
|
assert.equal(result, "ok");
|
|
} finally {
|
|
await new Promise((resolve, reject) => {
|
|
server.close((err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
test("proxy settings route blocks socks5 with backend flag disabled", async () => {
|
|
await resetStorage();
|
|
|
|
await withEnv("ENABLE_SOCKS5_PROXY", "false", async () => {
|
|
const request = new Request("http://localhost/api/settings/proxy", {
|
|
method: "PUT",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
level: "global",
|
|
proxy: {
|
|
type: "socks5",
|
|
host: "127.0.0.1",
|
|
port: "1080",
|
|
},
|
|
}),
|
|
});
|
|
|
|
const response = await proxySettingsRoute.PUT(request);
|
|
const payload = (await response.json()) as any;
|
|
assert.equal(response.status, 400);
|
|
assert.match(payload.error.message, /SOCKS5 proxy is disabled/i);
|
|
});
|
|
});
|
|
|
|
test("proxy settings route accepts socks5 with backend flag enabled", async () => {
|
|
await resetStorage();
|
|
|
|
await withEnv("ENABLE_SOCKS5_PROXY", "true", async () => {
|
|
const request = new Request("http://localhost/api/settings/proxy", {
|
|
method: "PUT",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
level: "global",
|
|
proxy: {
|
|
type: "SOCKS5",
|
|
host: "127.0.0.1",
|
|
port: "1080",
|
|
},
|
|
}),
|
|
});
|
|
|
|
const response = await proxySettingsRoute.PUT(request);
|
|
const payload = (await response.json()) as any;
|
|
assert.equal(response.status, 200);
|
|
assert.equal(payload.global.type, "socks5");
|
|
});
|
|
});
|
|
|
|
test("proxy test route rejects socks5 when backend flag is disabled", async () => {
|
|
await withEnv("ENABLE_SOCKS5_PROXY", "false", async () => {
|
|
const request = new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
proxy: {
|
|
type: "socks5",
|
|
host: "127.0.0.1",
|
|
port: "1080",
|
|
},
|
|
}),
|
|
});
|
|
|
|
const response = await proxyTestRoute.POST(request);
|
|
const payload = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.match(payload.error.message, /SOCKS5 proxy is disabled/i);
|
|
});
|
|
});
|
|
|
|
test("proxy test route runs socks5 test when backend flag is enabled", async () => {
|
|
await withEnv("ENABLE_SOCKS5_PROXY", "true", async () => {
|
|
const request = new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
proxy: {
|
|
type: "socks5",
|
|
host: "127.0.0.1",
|
|
port: "1",
|
|
},
|
|
}),
|
|
});
|
|
|
|
const response = await proxyTestRoute.POST(request);
|
|
const payload = (await response.json()) as any;
|
|
|
|
assert.notEqual(response.status, 400);
|
|
assert.equal(payload.success, false);
|
|
assert.equal(payload.proxyUrl, "socks5://127.0.0.1:1");
|
|
});
|
|
});
|
|
|
|
test("proxy test route validates JSON, schema, and proxy types before dispatching", async () => {
|
|
await resetStorage();
|
|
|
|
const invalidJsonResponse = await proxyTestRoute.POST(
|
|
new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: "{",
|
|
})
|
|
);
|
|
const invalidJsonBody = (await invalidJsonResponse.json()) as any;
|
|
assert.equal(invalidJsonResponse.status, 400);
|
|
assert.equal(invalidJsonBody.error.message, "Invalid JSON body");
|
|
|
|
const invalidBodyResponse = await proxyTestRoute.POST(
|
|
new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ proxy: { port: "8080" } }),
|
|
})
|
|
);
|
|
const invalidBody = (await invalidBodyResponse.json()) as any;
|
|
assert.equal(invalidBodyResponse.status, 400);
|
|
assert.equal(invalidBody.error.message, "Invalid request");
|
|
|
|
const socks4Response = await proxyTestRoute.POST(
|
|
new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
proxy: {
|
|
type: "socks4",
|
|
host: "127.0.0.1",
|
|
port: "1080",
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
const socks4Body = (await socks4Response.json()) as any;
|
|
assert.equal(socks4Response.status, 400);
|
|
assert.match(socks4Body.error.message, /proxy\.type must be http(, https, or socks5| or https)/i);
|
|
|
|
const unsupportedResponse = await proxyTestRoute.POST(
|
|
new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
proxy: {
|
|
type: "ftp",
|
|
host: "127.0.0.1",
|
|
port: "21",
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
const unsupportedBody = (await unsupportedResponse.json()) as any;
|
|
assert.equal(unsupportedResponse.status, 400);
|
|
assert.match(
|
|
unsupportedBody.error.message,
|
|
/proxy\.type must be http(, https, or socks5| or https)/i
|
|
);
|
|
});
|
|
|
|
test("proxy test route handles invalid proxy ports and uses stored proxy config when proxyId is provided", async () => {
|
|
await resetStorage();
|
|
|
|
const invalidPortResponse = await proxyTestRoute.POST(
|
|
new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
proxy: {
|
|
type: "http",
|
|
host: "127.0.0.1",
|
|
port: "70000",
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
const invalidPortBody = (await invalidPortResponse.json()) as any;
|
|
assert.equal(invalidPortResponse.status, 400);
|
|
assert.match(invalidPortBody.error.message, /invalid proxy port/i);
|
|
|
|
const storedProxy = await localDb.createProxy({
|
|
name: "Stored Proxy",
|
|
type: "http",
|
|
host: "127.0.0.1",
|
|
port: 1,
|
|
username: "alice",
|
|
password: "secret",
|
|
});
|
|
|
|
const proxyIdResponse = await proxyTestRoute.POST(
|
|
new Request("http://localhost/api/settings/proxy/test", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
proxyId: storedProxy.id,
|
|
proxy: {
|
|
host: "127.0.0.1",
|
|
port: 0,
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
const proxyIdBody = (await proxyIdResponse.json()) as any;
|
|
assert.notEqual(proxyIdResponse.status, 400);
|
|
assert.equal(proxyIdBody.success, false);
|
|
assert.equal(proxyIdBody.proxyUrl, "http://127.0.0.1:1");
|
|
});
|