Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2475,10 +2475,15 @@ jobs:
DISABLE_SCHEMA_UPDATE: "true"
SERVER_ROOT_PATH: ""
PROXY_LOGOUT_URL: ""
command: |
uv run --no-sync python -m litellm.proxy.proxy_cli \
--config ui/litellm-dashboard/e2e_tests/fixtures/config.yml \
--port 4000
# LITELLM_LICENSE is forwarded from the project env so premium-gated
# UI flows can be exercised. license.spec.ts asserts the resulting
# JWT carries premium_user=true; if it ever stops being passed, that
# test fails loudly rather than silently regressing premium coverage.
command: |
LITELLM_LICENSE="$LITELLM_LICENSE" \
uv run --no-sync python -m litellm.proxy.proxy_cli \
--config ui/litellm-dashboard/e2e_tests/fixtures/config.yml \
--port 4000
background: true
- run:
name: Wait for proxy to be ready
Expand All @@ -2495,9 +2500,12 @@ jobs:
exit 1
- run:
name: Run Playwright E2E tests
# Forward LITELLM_LICENSE so license.spec.ts can detect that the
# proxy was launched with a license and assert premium_user=true.
command: |
cd ui/litellm-dashboard
npx playwright test --config e2e_tests/playwright.config.ts
LITELLM_LICENSE="$LITELLM_LICENSE" \
npx playwright test --config e2e_tests/playwright.config.ts
no_output_timeout: 10m
- store_artifacts:
path: ui/litellm-dashboard/test-results
Expand Down
4 changes: 4 additions & 0 deletions ui/litellm-dashboard/e2e_tests/run_e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ export DISABLE_SCHEMA_UPDATE="true"
export SERVER_ROOT_PATH=""
# Prevent logout from redirecting to an external URL
export PROXY_LOGOUT_URL=""
# Forward LITELLM_LICENSE if set in the outer env so premium-gated UI flows
# (e.g. Team-BYOK Model switch) can be exercised. Tests that depend on a
# premium proxy gate themselves on process.env.LITELLM_LICENSE.
export LITELLM_LICENSE="${LITELLM_LICENSE:-}"

# --- Rebuild UI from source ---
echo "=== Building UI from source ==="
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { test, expect } from "@playwright/test";
import * as fs from "fs";
import { ADMIN_STORAGE_PATH } from "../../constants";

/**
* Sanity check that LITELLM_LICENSE is being forwarded to the proxy when set
* in the environment (e.g. CircleCI's `e2e_ui_testing` job). The login JWT's
* `premium_user` claim is the same value the dashboard reads to enable
* premium-gated UI surfaces (Team-BYOK switch, etc.), so asserting it here
* catches any future regression where the env var stops being plumbed
* through `run_e2e.sh` / `.circleci/config.yml`.
*
* Skips locally when no license is configured.
*/
test.describe("Premium license wiring", () => {
test("admin session JWT carries premium_user=true when LITELLM_LICENSE is set", () => {
test.skip(
!process.env.LITELLM_LICENSE,
"LITELLM_LICENSE not set in test env — proxy is running unlicensed",
);

const storage = JSON.parse(fs.readFileSync(ADMIN_STORAGE_PATH, "utf-8"));
const tokenCookie = storage.cookies?.find((c: { name: string }) => c.name === "token");
expect(tokenCookie, "token cookie missing from admin storage state").toBeDefined();

// Decode the JWT payload (no signature check — we trust globalSetup ran
// against our own proxy). Payload is the middle base64url segment.
const jwtParts = tokenCookie.value.split(".");
expect(jwtParts.length, "token cookie is not a 3-part JWT").toBe(3);
const [, payloadB64] = jwtParts;
const payload = JSON.parse(
Buffer.from(payloadB64, "base64url").toString("utf-8"),
);

expect(payload.premium_user).toBe(true);
});
});
Loading