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
4 changes: 3 additions & 1 deletion tests/e2e/src/cases/apikey-budget-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ describe("apikey max_budget_usd e2e: standalone admin rejects removed field", ()
etcdReachable = await new EtcdClient().ping();
if (!etcdReachable) return;

app = await spawnApp();
// Held-back: this test drives the Admin API surface itself, so it
// keeps the admin listener bound (the suite default is now admin-off).
app = await spawnApp({ admin: true });
admin = new AdminClient(app.adminUrl, app.adminKey);
});

Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/src/cases/apikey-lifecycle-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ describe("api key lifecycle e2e: expired/disabled keys fail closed, rotate swaps
if (!etcdReachable) return;

upstream = await startOpenAiUpstream();
app = await spawnApp();
// Held-back: this test drives the Admin API surface itself, so it
// keeps the admin listener bound (the suite default is now admin-off).
app = await spawnApp({ admin: true });
admin = new AdminClient(app.adminUrl, app.adminKey);
seed = new SeedClient(etcd, app.etcdPrefix);

Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/src/cases/auth-baseline-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ describe("auth baseline e2e: missing/malformed/unknown bearer all fail closed",
if (!etcdReachable) return;

upstream = await startOpenAiUpstream();
app = await spawnApp();
// Held-back: this test drives the Admin API surface itself, so it
// keeps the admin listener bound (the suite default is now admin-off).
app = await spawnApp({ admin: true });
seed = new SeedClient(etcd, app.etcdPrefix);

// A single Model + ProviderKey + ApiKey configured. The valid
Expand Down
8 changes: 7 additions & 1 deletion tests/e2e/src/cases/file-resource-source-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ describe("file resource source: smoke + admin write-guard", () => {

beforeAll(async () => {
upstream = await startOpenAiUpstream();
// Held-back: exercises the file-mode admin write-rejection (409), so
// the admin listener stays bound (the suite default is now admin-off).
app = await spawnApp({
admin: true,
resourcesFile: fileModeResources(upstream.baseUrl),
extraEnv: { [CALLER_KEY_ENV]: CALLER_PLAINTEXT },
});
Expand Down Expand Up @@ -255,6 +258,7 @@ describe("file resource source: differential vs etcd mode", () => {
// The SAME logical resource set on both gateways: one provider key,
// an allowed model, a forbidden model, one caller key.
fileApp = await spawnApp({
admin: true,
resourcesFile: `
_format_version: "1"
provider_keys:
Expand All @@ -278,7 +282,7 @@ api_keys:
`,
});

etcdApp = await spawnApp();
etcdApp = await spawnApp({ admin: true });
const seed = new SeedClient(etcd, etcdApp.etcdPrefix);
const pk = await seed.createProviderKey({
display_name: "diff-pk",
Expand Down Expand Up @@ -411,6 +415,7 @@ api_keys:
beforeAll(async () => {
upstream = await startOpenAiUpstream();
app = await spawnApp({
admin: true,
resourcesFile: reloadFile(upstream.baseUrl, ["reload-a"]),
});
});
Expand Down Expand Up @@ -487,6 +492,7 @@ describe("file resource source: fail-fast boot", () => {
let caught: unknown;
try {
await spawnApp({
admin: true,
resourcesFile: `
_format_version: "1"
models:
Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/src/cases/health-minimal-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ describe("livez e2e: public liveness route is /livez and /health is gone", () =>
beforeAll(async () => {
etcdReachable = await new EtcdClient().ping();
if (!etcdReachable) return;
app = await spawnApp();
// Held-back: this test drives the admin listener's health endpoint,
// so it keeps admin bound (the suite default is now admin-off).
app = await spawnApp({ admin: true });
});

afterAll(async () => {
Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/src/cases/openai-sdk-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ describe("openai SDK compat: drive gateway through real client", () => {
],
});

app = await spawnApp();
// Held-back: this test drives the Admin API surface itself, so it
// keeps the admin listener bound (the suite default is now admin-off).
app = await spawnApp({ admin: true });
// Deliberately seeds via the Admin API: deprecation-window coverage.
admin = new AdminClient(app.adminUrl, app.adminKey);

Expand Down
6 changes: 5 additions & 1 deletion tests/e2e/src/cases/prometheus-metrics-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ describe("prometheus metrics e2e", () => {
upstream = await startOpenAiUpstream({
nonStreamBody: responseBody(),
});
app = await spawnApp();
// Held-back: the "admin listener does not serve /metrics" test fetches
// `${app.adminUrl}/metrics` and expects 404, which needs the admin
// listener bound (unbound → connection refused, never a 404). The suite
// default is now admin-off, so this test opts back in.
app = await spawnApp({ admin: true });
seed = new SeedClient(etcd, app.etcdPrefix);

await configureOpenAi(seed, upstream, "prometheus-gpt");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ describe("seed-vs-admin characterization: direct etcd writes ≡ Admin API write
if (!etcdReachable) return;

upstream = await startOpenAiUpstream();
app = await spawnApp();
// Held-back: this test's subject is seed-vs-admin equivalence, so it
// keeps the admin listener bound (the suite default is now admin-off).
app = await spawnApp({ admin: true });
admin = new AdminClient(app.adminUrl, app.adminKey);
seed = new SeedClient(etcd, app.etcdPrefix);

Expand Down
5 changes: 4 additions & 1 deletion tests/e2e/src/cases/status-models-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ describe("status/models: etcd mode — equivalence with the admin endpoint", ()
},
});

app = await spawnApp();
// Held-back: compares /status/models to /admin/v1/models/status, so it
// keeps admin bound (the suite default is now admin-off).
app = await spawnApp({ admin: true });
const seed = new SeedClient(etcd, app.etcdPrefix);

const failPk = await seed.createProviderKey({
Expand Down Expand Up @@ -201,6 +203,7 @@ describe("status/models: standalone file source", () => {

beforeAll(async () => {
app = await spawnApp({
admin: true,
resourcesFile: `
_format_version: "1"
provider_keys:
Expand Down
30 changes: 22 additions & 8 deletions tests/e2e/src/harness/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import { harnessRequest } from "./http.js";

export interface AppOverrides {
/**
* Whether to bind the admin listener. Defaults to `true`. When `false`,
* the gateway runs with `admin.enabled = false` — no admin listener is
* bound even in etcd/file mode, mirroring the post-removal world. Seed
* resources through `SeedClient`/`EtcdClient` (never the Admin API), and
* readiness is gated on the proxy `/livez` plus the metrics listener
* instead of the admin health endpoint. `adminUrl`/`adminKey` are still
* returned but point at an unbound port.
* Whether to bind the admin listener. **Defaults to `false`** — the
* gateway runs with `admin.enabled = false`, no admin listener bound,
* mirroring the post-removal world; readiness gates on the proxy
* `/livez` plus the metrics listener, and `adminUrl`/`adminKey` are
* still returned but point at an unbound port. Resources are seeded
* through `SeedClient`/`EtcdClient`, never the Admin API.
*
* Only tests whose subject IS the Admin API surface (the held-back set —
* admin auth, write-rejection, deprecation headers, status-equivalence,
* key rotation) opt back in with `admin: true`; they stay admin-on until
* the Admin API is removed, then get deleted.
*/
admin?: boolean;
/** Inserted into `admin.admin_keys`. Defaults to a fresh random key. */
Expand Down Expand Up @@ -149,7 +153,17 @@ async function spawnAppOnce(overrides: AppOverrides = {}): Promise<SpawnedApp> {
}

const prometheusEnabled = overrides.prometheus ?? true;
const adminEnabled = overrides.admin ?? true;
const adminEnabled = overrides.admin ?? false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// `extra` is spread over the generated config at the top level, so an
// `extra.admin` would replace the generated admin block and could bind
// the listener while readiness still keys off `adminEnabled` and skips
// the admin health gate. Keep the `admin` boolean the single source of
// truth for the admin listener.
if (overrides.extra && "admin" in overrides.extra) {
throw new Error(
"spawnApp: control the admin listener with the `admin` boolean override, not `extra.admin`",
);
}
const [proxyPort, adminPort, metricsPort] = await pickFreePorts(3);
const adminKey = overrides.adminKey ?? `admin-${randomUUID()}`;
const etcdPrefix = `/aisix-e2e-${randomUUID()}`;
Expand Down
Loading