mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 21:22:28 +03:00
* fix(providers): validate bailian-coding-plan against the Token Plan host The catalog entry is the personal Alibaba Token Plan, but the region map still resolved the retired Coding Plan hosts. #10290 moved only the open-sse registry (inference) to token-plan.ap-southeast-1.maas.aliyuncs.com, leaving the dashboard's key validation pointed at coding-intl.dashscope.aliyuncs.com. That host rejects Token Plan keys with 401, and validateBailianCodingPlanProvider maps 401/403 to "Invalid API key" — so adding a working key failed at the modal while the same key served inference fine. Verified live 2026-08-18 with a valid key: legacy host 401 invalid_api_key, Token Plan host 429 quota (auth OK). - point both regions of ALIBABA_PROVIDER_ENDPOINTS at the Token Plan hosts, matching what docs/providers/ALIBABA-QWEN-PROVIDER-FAMILIES.md already stated - keep the retired hosts recognized as presets, so connections saved with the old URL still follow the region selector instead of being pinned to a dead host - keep image/video generation on the DashScope AIGC hosts, which the Token Plan host does not serve - probe with a model this plan actually serves (qwen3-coder-plus was Coding Plan) * test(providers): compare parsed hostnames in the legacy-host guard CodeQL flags URL .includes() checks as js/incomplete-url-substring-sanitization. The guard is an assertion, not a sanitizer, but comparing new URL().hostname is strictly more precise anyway — same coverage, no substring pattern. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me>
231 lines
8.4 KiB
TypeScript
231 lines
8.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { gotoDashboardRoute } from "./helpers/dashboardAuth";
|
|
|
|
// #7882 replaced this provider's free-text Base URL field with a region step:
|
|
// the endpoint is now derived from the choice ("global-sg" ->
|
|
// token-plan.ap-southeast-1.maas.aliyuncs.com, "china-beijing" ->
|
|
// token-plan.cn-beijing.maas.aliyuncs.com,
|
|
// see src/shared/constants/alibabaProviderRegions.ts), so the modal persists
|
|
// providerSpecificData.region instead of a baseUrl. A per-connection base-URL
|
|
// override still exists, but it moved to Advanced in the edit-connection modal.
|
|
|
|
test.describe("Bailian Coding Plan Provider", () => {
|
|
test.describe.configure({ mode: "serial" });
|
|
|
|
test("region step persists the international (Singapore) choice", async ({ page }) => {
|
|
const capturedPayloads: { createProvider?: Record<string, unknown> } = {};
|
|
|
|
await page.route("**/api/providers", async (route) => {
|
|
const method = route.request().method();
|
|
if (method === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ connections: [] }),
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (method === "POST") {
|
|
const payload = route.request().postDataJSON();
|
|
capturedPayloads.createProvider = payload;
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
connection: {
|
|
id: "conn-bailian-test",
|
|
provider: "bailian-coding-plan",
|
|
name: payload.name || "Test Connection",
|
|
testStatus: "active",
|
|
providerSpecificData: payload.providerSpecificData,
|
|
},
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fulfill({ status: 405 });
|
|
});
|
|
|
|
await page.route("**/api/providers/validate", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ valid: true }),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/provider-nodes", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ nodes: [] }),
|
|
});
|
|
});
|
|
|
|
await gotoDashboardRoute(page, "/dashboard/providers/bailian-coding-plan");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
|
|
// Dismiss any pre-existing dialog/overlay that may appear on page load
|
|
const preExistingDialog = page.getByRole("dialog").first();
|
|
if (await preExistingDialog.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await page.keyboard.press("Escape");
|
|
await preExistingDialog.waitFor({ state: "hidden", timeout: 3000 }).catch(() => {});
|
|
}
|
|
|
|
const addKeyButton = page.getByRole("button", {
|
|
name: /add.*api.*key|add.*key|add.*connection|connect|adicionar.*chave/i,
|
|
});
|
|
|
|
// Wait for the button to appear instead of immediately checking visibility
|
|
await addKeyButton.first().waitFor({ state: "visible", timeout: 15000 });
|
|
await expect(addKeyButton.first()).toBeEnabled({ timeout: 5000 });
|
|
await addKeyButton.first().click();
|
|
|
|
const dialog = page.getByRole("dialog").first();
|
|
await expect(dialog).toBeVisible({ timeout: 10000 });
|
|
|
|
const regionStep = dialog.getByTestId("alibaba-region-step");
|
|
await expect(regionStep).toBeVisible({ timeout: 15000 });
|
|
await expect(regionStep.locator("[data-region]")).toHaveCount(2);
|
|
await regionStep.locator('[data-region="global-sg"]').click();
|
|
|
|
const nameInput = dialog.getByLabel(/name/i).or(dialog.locator("input").first());
|
|
await nameInput.fill("Test Bailian Connection");
|
|
|
|
const apiKeyInput = dialog
|
|
.getByLabel(/api.*key/i)
|
|
.or(dialog.locator('input[type="password"]').first());
|
|
await apiKeyInput.fill("test-api-key-12345");
|
|
|
|
const saveButton = dialog
|
|
.getByRole("button", {
|
|
name: /save|add|create|connect/i,
|
|
})
|
|
.last();
|
|
await expect(saveButton).toBeEnabled({ timeout: 15000 });
|
|
await saveButton.click();
|
|
|
|
await expect(dialog)
|
|
.toBeHidden({ timeout: 10000 })
|
|
.catch(() => undefined);
|
|
|
|
expect(capturedPayloads.createProvider).toBeDefined();
|
|
const payload = capturedPayloads.createProvider;
|
|
expect(payload?.providerSpecificData).toBeDefined();
|
|
expect((payload?.providerSpecificData as Record<string, unknown>)?.region).toBe("global-sg");
|
|
});
|
|
|
|
// The old "invalid URL blocks save" case tested client-side validation of the
|
|
// free-text Base URL field, which #7882 removed for this provider — an invalid
|
|
// URL is no longer reachable from this modal. Replaced with the other half of
|
|
// the region contract: the China-mainland choice must persist as typed, since
|
|
// that is what selects the token-plan.cn-beijing.maas.aliyuncs.com endpoint.
|
|
test("region step persists the China-mainland (Beijing) choice", async ({ page }) => {
|
|
const capturedPayloads: { createProvider?: Record<string, unknown> } = {};
|
|
|
|
await page.route("**/api/providers", async (route) => {
|
|
const method = route.request().method();
|
|
if (method === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ connections: [] }),
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (method === "POST") {
|
|
const payload = route.request().postDataJSON();
|
|
capturedPayloads.createProvider = payload;
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
connection: {
|
|
id: "conn-bailian-cn",
|
|
provider: "bailian-coding-plan",
|
|
name: payload.name || "Test Connection",
|
|
testStatus: "active",
|
|
providerSpecificData: payload.providerSpecificData,
|
|
},
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fulfill({ status: 405 });
|
|
});
|
|
|
|
await page.route("**/api/providers/validate", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ valid: true }),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/provider-nodes", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ nodes: [] }),
|
|
});
|
|
});
|
|
|
|
await gotoDashboardRoute(page, "/dashboard/providers/bailian-coding-plan");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
|
|
// Dismiss any pre-existing dialog/overlay that may appear on page load
|
|
const preExistingDialog = page.getByRole("dialog").first();
|
|
if (await preExistingDialog.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await page.keyboard.press("Escape");
|
|
await preExistingDialog.waitFor({ state: "hidden", timeout: 3000 }).catch(() => {});
|
|
}
|
|
|
|
const addKeyButton = page.getByRole("button", {
|
|
name: /add.*api.*key|add.*key|add.*connection|connect|adicionar.*chave/i,
|
|
});
|
|
|
|
// Wait for the button to appear instead of immediately checking visibility
|
|
await addKeyButton.first().waitFor({ state: "visible", timeout: 15000 });
|
|
await expect(addKeyButton.first()).toBeEnabled({ timeout: 5000 });
|
|
await addKeyButton.first().click();
|
|
|
|
const dialog = page.getByRole("dialog").first();
|
|
await expect(dialog).toBeVisible({ timeout: 10000 });
|
|
|
|
const regionStep = dialog.getByTestId("alibaba-region-step");
|
|
await expect(regionStep).toBeVisible({ timeout: 15000 });
|
|
await regionStep.locator('[data-region="china-beijing"]').click();
|
|
|
|
const nameInput = dialog.getByLabel(/name/i).or(dialog.locator("input").first());
|
|
await nameInput.fill("Test Bailian CN Connection");
|
|
|
|
const apiKeyInput = dialog
|
|
.getByLabel(/api.*key/i)
|
|
.or(dialog.locator('input[type="password"]').first());
|
|
await apiKeyInput.fill("test-api-key-12345");
|
|
|
|
const saveButton = dialog
|
|
.getByRole("button", {
|
|
name: /save|add|create|connect/i,
|
|
})
|
|
.last();
|
|
await expect(saveButton).toBeEnabled({ timeout: 15000 });
|
|
await saveButton.click();
|
|
|
|
await expect(dialog)
|
|
.toBeHidden({ timeout: 10000 })
|
|
.catch(() => undefined);
|
|
|
|
expect(capturedPayloads.createProvider).toBeDefined();
|
|
const payload = capturedPayloads.createProvider;
|
|
expect(payload?.providerSpecificData).toBeDefined();
|
|
expect((payload?.providerSpecificData as Record<string, unknown>)?.region).toBe(
|
|
"china-beijing"
|
|
);
|
|
});
|
|
});
|