mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
test(ui): harden Router Settings e2e and make its typing a real CI gate
Address an adversarial review of the Loadbalancing e2e: - The "typed against the backend schema" claim was hollow: nothing type-checked e2e_tests (the root tsconfig excludes it and no CI step runs tsc), so a contract drift would compile and run unchanged. Add e2e_tests/tsconfig.json, a typecheck:e2e script, and a CircleCI step so the schema typing actually gates. - The two describe blocks both mutate the proxy's shared router_settings, and the Loadbalancing save echoes the whole settings object, so they could clobber each other under local fullyParallel. Run the file serially. - patchRouterSettings swallowed a failed seed, which surfaced later as a misleading UI timeout. Assert the write succeeded, and rely on the server-side merge instead of echoing the whole settings object back (drops a cast and a GET). - Empty routing_groups already reproduces the bug, so drop the non-empty seed and its model coupling.
This commit is contained in:
parent
540c860a97
commit
3971469b71
4 changed files with 39 additions and 15 deletions
|
|
@ -2687,6 +2687,14 @@ jobs:
|
|||
paths:
|
||||
- ui/litellm-dashboard/node_modules
|
||||
- ~/.cache/ms-playwright
|
||||
- run:
|
||||
name: Type-check E2E specs
|
||||
# The specs type their request/response round-trips against the generated
|
||||
# OpenAPI schema; this step turns that typing into a real gate, so a backend
|
||||
# contract drift fails here instead of silently passing at runtime.
|
||||
command: |
|
||||
cd ui/litellm-dashboard
|
||||
npm run typecheck:e2e
|
||||
- run:
|
||||
name: Build UI from source
|
||||
# Prior version used `cp -r out/ ../../litellm/proxy/_experimental/out/`.
|
||||
|
|
|
|||
|
|
@ -3,11 +3,16 @@ import { ADMIN_STORAGE_PATH } from "../../constants";
|
|||
import { navigateToPage } from "../../helpers/navigation";
|
||||
import { Page } from "../../fixtures/pages";
|
||||
import { Role, users } from "../../fixtures/users";
|
||||
// Type-only import of the OpenAPI-generated backend schema; esbuild erases it at
|
||||
// runtime, so the round-trip below is checked against the real /config/update and
|
||||
// /router/settings contracts without bundling the 2 MB definition file.
|
||||
// Type-only import of the OpenAPI-generated backend schema. esbuild erases it at
|
||||
// runtime; the round-trip below is enforced by the `typecheck:e2e` CI step (tsc over
|
||||
// e2e_tests), so a drift in the /config/update or /router/settings contract fails the
|
||||
// build rather than silently passing here.
|
||||
import type { components } from "../../../src/lib/http/schema";
|
||||
|
||||
// These tests mutate the proxy's shared router_settings, and the Loadbalancing save
|
||||
// echoes the whole settings object, so they must not run concurrently.
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
const PRIMARY = "fake-openai-gpt-4";
|
||||
const FALLBACK = "fake-anthropic-claude";
|
||||
|
||||
|
|
@ -111,32 +116,33 @@ const BASE_URL = "http://localhost:4000";
|
|||
const ADMIN_AUTH = { Authorization: `Bearer ${users[Role.ProxyAdmin].password}` };
|
||||
|
||||
/**
|
||||
* Merge a router_settings patch into the live config through the typed
|
||||
* /config/update contract, preserving any other settings already present.
|
||||
* Apply a router_settings patch through the typed /config/update contract. The
|
||||
* server merges it over existing settings (request wins), so only the passed keys
|
||||
* change. Fails loudly if the write is rejected instead of leaving a silent bad seed.
|
||||
*/
|
||||
async function patchRouterSettings(
|
||||
request: import("@playwright/test").APIRequestContext,
|
||||
patch: Partial<NonNullable<ConfigYAML["router_settings"]>>,
|
||||
) {
|
||||
const current = await request.get(`${BASE_URL}/get/config/callbacks`, { headers: ADMIN_AUTH });
|
||||
const existing = current.ok() ? (await current.json())?.router_settings ?? {} : {};
|
||||
const payload = { router_settings: { ...(existing as Record<string, unknown>), ...patch } };
|
||||
await request.post(`${BASE_URL}/config/update`, { headers: ADMIN_AUTH, data: payload });
|
||||
const res = await request.post(`${BASE_URL}/config/update`, {
|
||||
headers: ADMIN_AUTH,
|
||||
data: { router_settings: patch },
|
||||
});
|
||||
expect(res.ok(), `seed /config/update failed: ${res.status()} ${await res.text()}`).toBeTruthy();
|
||||
}
|
||||
|
||||
test.describe("Router Settings - Loadbalancing", () => {
|
||||
test.use({ storageState: ADMIN_STORAGE_PATH });
|
||||
|
||||
// Seed a present routing_groups array (the LIT-4057 trigger) plus a known
|
||||
// num_retries so the UI assertions are deterministic across reruns.
|
||||
const ROUTING_GROUP = { group_name: "e2e-lit-4057", models: [PRIMARY], routing_strategy: "simple-shuffle" };
|
||||
|
||||
// Pin num_retries and an empty routing_groups so the assertions are deterministic.
|
||||
// Empty already reproduces LIT-4057: the old tab serialized [] to the string "[]"
|
||||
// and the save 422'd.
|
||||
test.beforeEach(async ({ request }) => {
|
||||
await patchRouterSettings(request, { num_retries: 3, routing_groups: [ROUTING_GROUP] });
|
||||
await patchRouterSettings(request, { num_retries: 3, routing_groups: [] });
|
||||
});
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
await patchRouterSettings(request, { num_retries: 3, routing_groups: [] });
|
||||
await patchRouterSettings(request, { num_retries: 3 });
|
||||
});
|
||||
|
||||
test("saves the Loadbalancing tab without a 422 when routing_groups is present, and persists", async ({
|
||||
|
|
|
|||
9
ui/litellm-dashboard/e2e_tests/tsconfig.json
Normal file
9
ui/litellm-dashboard/e2e_tests/tsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
"e2e:ui": "playwright test --ui --config e2e_tests/playwright.config.ts",
|
||||
"e2e:migration": "playwright test e2e_tests/tests/migration/migratedPages.spec.ts --config e2e_tests/playwright.config.ts",
|
||||
"e2e:migration:root": "playwright test --config e2e_tests/migration.serverRootPath.config.ts",
|
||||
"typecheck:e2e": "tsc -p e2e_tests/tsconfig.json --noEmit",
|
||||
"knip": "knip",
|
||||
"knip:fix": "knip --fix",
|
||||
"gen:api": "node scripts/gen-api-types.mjs"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue