mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
feat: migrate iFlow provider to Qoder AI (#660)
This commit is contained in:
150
tests/unit/qoder-executor.test.mjs
Normal file
150
tests/unit/qoder-executor.test.mjs
Normal file
@@ -0,0 +1,150 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import crypto from "node:crypto";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// QoderExecutor Unit Tests
|
||||
// Tests for HMAC-SHA256 signature, headers, URL building
|
||||
// Fixes: https://github.com/diegosouzapw/OmniRoute/issues/114
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
const { QoderExecutor } = await import("../../open-sse/executors/qoder.ts");
|
||||
|
||||
// ─── Constructor ──────────────────────────────────────────────
|
||||
|
||||
test("QoderExecutor: constructor sets provider to 'qoder'", () => {
|
||||
const executor = new QoderExecutor();
|
||||
assert.equal(executor.getProvider(), "qoder");
|
||||
});
|
||||
|
||||
// ─── createQoderSignature ─────────────────────────────────────
|
||||
|
||||
test("QoderExecutor: createQoderSignature returns valid HMAC-SHA256 hex", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const userAgent = "Qoder-Cli";
|
||||
const sessionID = "session-test-123";
|
||||
const timestamp = 1700000000000;
|
||||
const apiKey = "test-api-key-secret";
|
||||
|
||||
const signature = executor.createQoderSignature(userAgent, sessionID, timestamp, apiKey);
|
||||
|
||||
// Verify it's a valid hex string (64 chars for SHA256)
|
||||
assert.match(signature, /^[0-9a-f]{64}$/);
|
||||
|
||||
// Verify reproducibility — same inputs produce same signature
|
||||
const signature2 = executor.createQoderSignature(userAgent, sessionID, timestamp, apiKey);
|
||||
assert.equal(signature, signature2);
|
||||
|
||||
// Verify against manual HMAC computation
|
||||
const payload = `${userAgent}:${sessionID}:${timestamp}`;
|
||||
const expected = crypto.createHmac("sha256", apiKey).update(payload).digest("hex");
|
||||
assert.equal(signature, expected);
|
||||
});
|
||||
|
||||
test("QoderExecutor: createQoderSignature returns empty string when apiKey is empty", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const result = executor.createQoderSignature("agent", "session", 123, "");
|
||||
assert.equal(result, "");
|
||||
});
|
||||
|
||||
test("QoderExecutor: createQoderSignature returns empty string when apiKey is null", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const result = executor.createQoderSignature("agent", "session", 123, null);
|
||||
assert.equal(result, "");
|
||||
});
|
||||
|
||||
// ─── buildHeaders ─────────────────────────────────────────────
|
||||
|
||||
test("QoderExecutor: buildHeaders includes qoder-specific headers", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const credentials = { apiKey: "test-key-123" };
|
||||
|
||||
const headers = executor.buildHeaders(credentials, true);
|
||||
|
||||
// Must include required qoder headers
|
||||
assert.ok(headers["session-id"], "Missing session-id header");
|
||||
assert.ok(headers["x-qoder-timestamp"], "Missing x-qoder-timestamp header");
|
||||
assert.ok(headers["x-qoder-signature"], "Missing x-qoder-signature header");
|
||||
|
||||
// session-id format
|
||||
assert.ok(
|
||||
headers["session-id"].startsWith("session-"),
|
||||
"session-id should start with 'session-'"
|
||||
);
|
||||
|
||||
// timestamp is a number string
|
||||
assert.match(headers["x-qoder-timestamp"], /^\d+$/);
|
||||
|
||||
// signature is hex
|
||||
assert.match(headers["x-qoder-signature"], /^[0-9a-f]{64}$/);
|
||||
|
||||
// Authorization
|
||||
assert.equal(headers["Authorization"], "Bearer test-key-123");
|
||||
|
||||
// Content-Type
|
||||
assert.equal(headers["Content-Type"], "application/json");
|
||||
|
||||
// Streaming Accept
|
||||
assert.equal(headers["Accept"], "text/event-stream");
|
||||
});
|
||||
|
||||
test("QoderExecutor: buildHeaders omits Accept header when stream is false", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const credentials = { apiKey: "test-key" };
|
||||
|
||||
const headers = executor.buildHeaders(credentials, false);
|
||||
|
||||
assert.equal(headers["Accept"], undefined);
|
||||
});
|
||||
|
||||
test("QoderExecutor: buildHeaders uses accessToken when apiKey is missing", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const credentials = { accessToken: "oauth-token-123" };
|
||||
|
||||
const headers = executor.buildHeaders(credentials);
|
||||
|
||||
assert.equal(headers["Authorization"], "Bearer oauth-token-123");
|
||||
// Signature should still be generated using the accessToken
|
||||
assert.ok(headers["x-qoder-signature"].length > 0);
|
||||
});
|
||||
|
||||
test("QoderExecutor: buildHeaders generates unique session IDs per call", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const credentials = { apiKey: "key" };
|
||||
|
||||
const headers1 = executor.buildHeaders(credentials);
|
||||
const headers2 = executor.buildHeaders(credentials);
|
||||
|
||||
assert.notEqual(headers1["session-id"], headers2["session-id"]);
|
||||
});
|
||||
|
||||
// ─── buildUrl ─────────────────────────────────────────────────
|
||||
|
||||
test("QoderExecutor: buildUrl returns config baseUrl", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const url = executor.buildUrl("qwen3-coder-plus", true);
|
||||
|
||||
assert.equal(url, "https://apis.qoder.cn/v1/chat/completions");
|
||||
});
|
||||
|
||||
// ─── transformRequest ─────────────────────────────────────────
|
||||
|
||||
test("QoderExecutor: transformRequest passes body through unchanged", () => {
|
||||
const executor = new QoderExecutor();
|
||||
const body = {
|
||||
model: "deepseek-r1",
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
stream: true,
|
||||
};
|
||||
|
||||
const result = executor.transformRequest("deepseek-r1", body, true, {});
|
||||
assert.deepEqual(result, body);
|
||||
});
|
||||
|
||||
// ─── Integration: executor registry ───────────────────────────
|
||||
|
||||
test("QoderExecutor: getExecutor('qoder') returns QoderExecutor instance", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const executor = getExecutor("qoder");
|
||||
assert.ok(executor instanceof QoderExecutor, "Should return QoderExecutor instance");
|
||||
});
|
||||
Reference in New Issue
Block a user