426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
503 lines
18 KiB
TypeScript
503 lines
18 KiB
TypeScript
/**
|
|
* Group E — Agent / admin / advertising / training e2e tests.
|
|
*
|
|
* Covers the 14 routes assigned to `agent-backend-dev` in FANOUT.md:
|
|
*
|
|
* /api/admin/redemptions
|
|
* /api/v1/admin/ai-pricing
|
|
* /api/v1/admin/docker-containers
|
|
* /api/v1/admin/docker-containers/:id/logs
|
|
* /api/v1/admin/docker-containers/audit
|
|
* /api/v1/admin/docker-nodes/:nodeId/health-check
|
|
* /api/v1/admin/infrastructure/containers/actions
|
|
* /api/v1/advertising/accounts/:id
|
|
* /api/v1/advertising/campaigns/:id (+ analytics, creatives, pause, start)
|
|
* /api/training/vertex/tune
|
|
* Three assertions per route family:
|
|
*
|
|
* 1. Auth gate — unauthenticated request returns exactly 401 (the global
|
|
* auth middleware in `src/middleware/auth.ts` runs before any handler;
|
|
* these are not public paths).
|
|
* 2. Happy path / behavior — for routes with a real handler we assert the
|
|
* bearer-authenticated response shape; Worker-boundary stubs
|
|
* (DockerSSHClient/node:fs blockers) answer exactly 501 not_yet_migrated.
|
|
* 3. Validation — for routes that take a body, send a known-bad payload and
|
|
* assert 400.
|
|
*
|
|
* Admin routes additionally require a wallet-bound admin user. The e2e preload
|
|
* seeds the local test user as admin (AGENT_TEST_BOOTSTRAP_ADMIN=true), then
|
|
* this file exchanges the bootstrapped API key for a session cookie.
|
|
*
|
|
* Skip behavior: with REQUIRE_E2E_SERVER=0 and no reachable Worker (or no
|
|
* bootstrapped TEST_API_KEY) every test in this file reports as a counted,
|
|
* named `skip` — never a silent pass.
|
|
*/
|
|
|
|
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
api,
|
|
bearerHeaders,
|
|
exchangeApiKeyForSession,
|
|
getBaseUrl,
|
|
isServerReachable,
|
|
memberBearerHeaders,
|
|
} from "./_helpers/api";
|
|
|
|
const serverReachable = await isServerReachable();
|
|
const hasTestApiKey = Boolean(process.env.TEST_API_KEY?.trim());
|
|
if (!serverReachable) {
|
|
console.warn(
|
|
`[group-e-agent-admin] ${getBaseUrl()} did not respond to /api/health. ` +
|
|
"Tests will SKIP. Start the Worker (bun run dev:api → wrangler dev) " +
|
|
"or set TEST_API_BASE_URL to a reachable host.",
|
|
);
|
|
}
|
|
if (!hasTestApiKey) {
|
|
console.warn(
|
|
"[group-e-agent-admin] TEST_API_KEY is not set; the preload could not " +
|
|
"bootstrap a test API key. Tests will SKIP.",
|
|
);
|
|
}
|
|
|
|
// Loud, counted skip instead of a silent pass when the Worker/key is absent.
|
|
const describeE2E = describe.skipIf(!serverReachable || !hasTestApiKey);
|
|
|
|
const adminSessionCookie = process.env.AGENT_TEST_ADMIN_SESSION?.trim() || null;
|
|
// The admin-session exchange requires PLAYWRIGHT_TEST_AUTH=true on the target
|
|
// Worker (always true in the run-e2e-batches lane). Failing while the Worker
|
|
// is up is a harness defect — fail loud, don't skip.
|
|
const sessionCookie: string | null =
|
|
serverReachable && hasTestApiKey
|
|
? adminSessionCookie || (await exchangeApiKeyForSession())
|
|
: null;
|
|
|
|
function adminHeaders(): Record<string, string> {
|
|
if (!sessionCookie) {
|
|
throw new Error(
|
|
"Admin session cookie missing; ensure the e2e preload exchanged the bootstrapped API key.",
|
|
);
|
|
}
|
|
return { Cookie: sessionCookie, "Content-Type": "application/json" };
|
|
}
|
|
|
|
const FAKE_UUID = "00000000-0000-4000-8000-000000000000";
|
|
|
|
/**
|
|
* Assert the global auth middleware rejected an unauthenticated request with
|
|
* exactly 401. (403 would mean a handler ran; 404 would mean the route is not
|
|
* mounted at all — both are regressions.)
|
|
*/
|
|
function expectAuthGate(status: number, path: string): void {
|
|
if (status !== 401) {
|
|
throw new Error(`Expected 401 from unauthenticated ${path}, got ${status}`);
|
|
}
|
|
expect(status).toBe(401);
|
|
}
|
|
|
|
describeE2E("Group E: admin / redemptions", () => {
|
|
test("GET /api/admin/redemptions rejects unauthenticated", async () => {
|
|
const res = await api.get("/api/admin/redemptions");
|
|
expectAuthGate(res.status, "GET /api/admin/redemptions");
|
|
});
|
|
|
|
test("GET /api/admin/redemptions rejects non-admin bearer with 403", async () => {
|
|
const res = await api.get("/api/admin/redemptions", {
|
|
headers: memberBearerHeaders(),
|
|
});
|
|
// The seeded member key authenticates fine (so not 401) but lacks the
|
|
// admin role → the route's own gate answers 403.
|
|
expect(res.status).toBe(403);
|
|
});
|
|
|
|
test("POST /api/admin/redemptions rejects invalid body with 400 (admin) or 401/403 (non-admin)", async () => {
|
|
const headers = adminHeaders();
|
|
const res = await api.post(
|
|
"/api/admin/redemptions",
|
|
{ redemptionId: "not-a-uuid", action: "approve" },
|
|
{ headers },
|
|
);
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test("GET /api/admin/redemptions returns redemption list for admin", async () => {
|
|
const res = await api.get("/api/admin/redemptions?status=pending&limit=5", {
|
|
headers: adminHeaders(),
|
|
});
|
|
expect(res.status).toBe(200);
|
|
const body = (await res.json()) as {
|
|
success?: boolean;
|
|
redemptions?: unknown[];
|
|
summary?: { statusCounts?: Record<string, unknown> };
|
|
};
|
|
expect(body.success).toBe(true);
|
|
expect(Array.isArray(body.redemptions)).toBe(true);
|
|
expect(body.summary).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: admin / ai-pricing", () => {
|
|
test("GET /api/v1/admin/ai-pricing rejects unauthenticated", async () => {
|
|
const res = await api.get("/api/v1/admin/ai-pricing");
|
|
expectAuthGate(res.status, "GET /api/v1/admin/ai-pricing");
|
|
});
|
|
|
|
test("GET /api/v1/admin/ai-pricing rejects non-admin bearer", async () => {
|
|
const res = await api.get("/api/v1/admin/ai-pricing", {
|
|
headers: memberBearerHeaders(),
|
|
});
|
|
expect(res.status).toBe(403);
|
|
});
|
|
|
|
test("PUT /api/v1/admin/ai-pricing rejects invalid body", async () => {
|
|
const headers = adminHeaders();
|
|
const res = await api.put(
|
|
"/api/v1/admin/ai-pricing",
|
|
{
|
|
billingSource: "not-a-real-source",
|
|
provider: "",
|
|
model: "",
|
|
unitPrice: -1,
|
|
},
|
|
{ headers },
|
|
);
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test("GET /api/v1/admin/ai-pricing returns pricing entries for admin", async () => {
|
|
const res = await api.get("/api/v1/admin/ai-pricing", {
|
|
headers: adminHeaders(),
|
|
});
|
|
expect(res.status).toBe(200);
|
|
const body = (await res.json()) as {
|
|
pricing?: unknown[];
|
|
refreshRuns?: unknown[];
|
|
};
|
|
expect(Array.isArray(body.pricing)).toBe(true);
|
|
expect(Array.isArray(body.refreshRuns)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: admin / cloud-observability", () => {
|
|
test("GET /api/v1/admin/cloud-observability rejects unauthenticated", async () => {
|
|
const res = await api.get("/api/v1/admin/cloud-observability");
|
|
expectAuthGate(res.status, "GET /api/v1/admin/cloud-observability");
|
|
});
|
|
|
|
test("GET /api/v1/admin/cloud-observability returns request telemetry for admin", async () => {
|
|
const res = await api.get("/api/v1/admin/cloud-observability?limit=25", {
|
|
headers: adminHeaders(),
|
|
});
|
|
expect(res.status).toBe(200);
|
|
const body = (await res.json()) as {
|
|
success?: boolean;
|
|
data?: {
|
|
requests?: unknown[];
|
|
slowRequests?: unknown[];
|
|
slowDb?: unknown[];
|
|
duplicateReadRequests?: unknown[];
|
|
burstyRequests?: unknown[];
|
|
};
|
|
};
|
|
expect(body.success).toBe(true);
|
|
expect(Array.isArray(body.data?.requests)).toBe(true);
|
|
expect(Array.isArray(body.data?.slowRequests)).toBe(true);
|
|
expect(Array.isArray(body.data?.slowDb)).toBe(true);
|
|
expect(Array.isArray(body.data?.duplicateReadRequests)).toBe(true);
|
|
expect(Array.isArray(body.data?.burstyRequests)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: admin / docker-containers (live + 501 stubs)", () => {
|
|
test("GET /api/v1/admin/docker-containers rejects unauthenticated", async () => {
|
|
const res = await api.get("/api/v1/admin/docker-containers");
|
|
expectAuthGate(res.status, "GET /api/v1/admin/docker-containers");
|
|
});
|
|
|
|
test("GET /api/v1/admin/docker-containers rejects non-super-admin bearer", async () => {
|
|
const res = await api.get("/api/v1/admin/docker-containers", {
|
|
headers: memberBearerHeaders(),
|
|
});
|
|
expect(res.status).toBe(403);
|
|
});
|
|
|
|
test("GET /api/v1/admin/docker-containers rejects invalid status filter (admin)", async () => {
|
|
const res = await api.get(
|
|
"/api/v1/admin/docker-containers?status=garbage",
|
|
{
|
|
headers: adminHeaders(),
|
|
},
|
|
);
|
|
// ValidationError(400): status is not in the allowed set (the seeded
|
|
// session is super-admin, so the role gate never fires here).
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test("GET /api/v1/admin/docker-containers/:id/logs is gated before route handling", async () => {
|
|
const unauthed = await api.get(
|
|
`/api/v1/admin/docker-containers/${FAKE_UUID}/logs`,
|
|
);
|
|
expectAuthGate(unauthed.status, "GET docker-containers/:id/logs (unauth)");
|
|
|
|
const authed = await api.get(
|
|
`/api/v1/admin/docker-containers/${FAKE_UUID}/logs`,
|
|
{
|
|
headers: bearerHeaders(),
|
|
},
|
|
);
|
|
expect(authed.status).toBe(501);
|
|
const body = (await authed.json()) as { error?: string };
|
|
expect(body.error).toBe("not_yet_migrated");
|
|
});
|
|
|
|
test("GET /api/v1/admin/docker-containers/audit returns the Worker boundary fallback", async () => {
|
|
const unauthed = await api.get("/api/v1/admin/docker-containers/audit");
|
|
expectAuthGate(unauthed.status, "GET docker-containers/audit (unauth)");
|
|
|
|
const authed = await api.get("/api/v1/admin/docker-containers/audit", {
|
|
headers: bearerHeaders(),
|
|
});
|
|
expect(authed.status).toBe(501);
|
|
});
|
|
|
|
test("POST /api/v1/admin/infrastructure/containers/actions rejects unauthenticated and returns the Worker boundary fallback", async () => {
|
|
const unauthed = await api.post(
|
|
"/api/v1/admin/infrastructure/containers/actions",
|
|
{
|
|
action: "restart",
|
|
containerId: FAKE_UUID,
|
|
},
|
|
);
|
|
expectAuthGate(
|
|
unauthed.status,
|
|
"POST infrastructure/containers/actions (unauth)",
|
|
);
|
|
|
|
const authed = await api.post(
|
|
"/api/v1/admin/infrastructure/containers/actions",
|
|
{ action: "restart", containerId: FAKE_UUID },
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
expect(authed.status).toBe(501);
|
|
const body = (await authed.json()) as { error?: string };
|
|
expect(body.error).toBe("not_yet_migrated");
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: advertising / accounts", () => {
|
|
test("GET /api/v1/advertising/accounts/:id rejects unauthenticated", async () => {
|
|
const res = await api.get(`/api/v1/advertising/accounts/${FAKE_UUID}`);
|
|
expectAuthGate(res.status, "GET advertising/accounts/:id");
|
|
});
|
|
|
|
test("GET /api/v1/advertising/accounts/:id returns 404 for unknown id", async () => {
|
|
const res = await api.get(`/api/v1/advertising/accounts/${FAKE_UUID}`, {
|
|
headers: bearerHeaders(),
|
|
});
|
|
// FAKE_UUID is well-formed, so validation passes and the ownership
|
|
// lookup misses → 404.
|
|
expect(res.status).toBe(404);
|
|
});
|
|
|
|
test("DELETE /api/v1/advertising/accounts/:id rejects unauthenticated", async () => {
|
|
const res = await api.delete(`/api/v1/advertising/accounts/${FAKE_UUID}`);
|
|
expectAuthGate(res.status, "DELETE advertising/accounts/:id");
|
|
});
|
|
|
|
test("POST /api/v1/advertising/accounts/:id/media rejects unauthenticated", async () => {
|
|
const res = await api.post(
|
|
`/api/v1/advertising/accounts/${FAKE_UUID}/media`,
|
|
{
|
|
type: "image",
|
|
url: "https://example.com/creative.png",
|
|
},
|
|
);
|
|
expectAuthGate(res.status, "POST advertising/accounts/:id/media");
|
|
});
|
|
|
|
test("POST /api/v1/advertising/accounts/:id/media rejects invalid body with 400", async () => {
|
|
const res = await api.post(
|
|
`/api/v1/advertising/accounts/${FAKE_UUID}/media`,
|
|
{ type: "audio", url: "not-a-url" },
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test("GET /api/v1/advertising/accounts/:id/media rejects missing status query", async () => {
|
|
const res = await api.get(
|
|
`/api/v1/advertising/accounts/${FAKE_UUID}/media`,
|
|
{
|
|
headers: bearerHeaders(),
|
|
},
|
|
);
|
|
expect(res.status).toBe(400);
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: advertising / campaigns", () => {
|
|
test("GET /api/v1/advertising/campaigns/:id rejects unauthenticated", async () => {
|
|
const res = await api.get(`/api/v1/advertising/campaigns/${FAKE_UUID}`);
|
|
expectAuthGate(res.status, "GET advertising/campaigns/:id");
|
|
});
|
|
|
|
test("GET /api/v1/advertising/campaigns/:id returns 404 for unknown id", async () => {
|
|
const res = await api.get(`/api/v1/advertising/campaigns/${FAKE_UUID}`, {
|
|
headers: bearerHeaders(),
|
|
});
|
|
expect(res.status).toBe(404);
|
|
});
|
|
|
|
test("PATCH /api/v1/advertising/campaigns/:id rejects invalid body with 400", async () => {
|
|
const res = await api.patch(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}`,
|
|
{ budgetAmount: "not-a-number", startDate: "definitely-not-an-iso-date" },
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
// The schema rejects the body before any campaign lookup runs.
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test("DELETE /api/v1/advertising/campaigns/:id rejects unauthenticated", async () => {
|
|
const res = await api.delete(`/api/v1/advertising/campaigns/${FAKE_UUID}`);
|
|
expectAuthGate(res.status, "DELETE advertising/campaigns/:id");
|
|
});
|
|
|
|
test("GET /api/v1/advertising/campaigns/:id/analytics rejects unauthenticated", async () => {
|
|
const res = await api.get(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}/analytics`,
|
|
);
|
|
expectAuthGate(res.status, "GET advertising/campaigns/:id/analytics");
|
|
});
|
|
|
|
test("GET /api/v1/advertising/campaigns/:id/analytics rejects bad date range with 400", async () => {
|
|
const res = await api.get(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}/analytics?startDate=2024-12-31T00:00:00Z&endDate=2024-01-01T00:00:00Z`,
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
expect(res.status).toBe(400);
|
|
const body = (await res.json()) as { error?: string };
|
|
expect(body.error).toContain("date");
|
|
});
|
|
|
|
test("GET /api/v1/advertising/campaigns/:id/creatives rejects unauthenticated", async () => {
|
|
const res = await api.get(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}/creatives`,
|
|
);
|
|
expectAuthGate(res.status, "GET advertising/campaigns/:id/creatives");
|
|
});
|
|
|
|
test("POST /api/v1/advertising/campaigns/:id/creatives rejects invalid body with 400", async () => {
|
|
const res = await api.post(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}/creatives`,
|
|
{ name: 0, type: "not-a-real-type" },
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
// The creative schema rejects the body before any campaign lookup runs.
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test("POST /api/v1/advertising/campaigns/:id/pause rejects unauthenticated", async () => {
|
|
const res = await api.post(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}/pause`,
|
|
);
|
|
expectAuthGate(res.status, "POST advertising/campaigns/:id/pause");
|
|
});
|
|
|
|
test("POST /api/v1/advertising/campaigns/:id/start rejects unauthenticated", async () => {
|
|
const res = await api.post(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}/start`,
|
|
);
|
|
expectAuthGate(res.status, "POST advertising/campaigns/:id/start");
|
|
});
|
|
|
|
test("POST /api/v1/advertising/campaigns/:id/start returns 404 for unknown campaign", async () => {
|
|
const res = await api.post(
|
|
`/api/v1/advertising/campaigns/${FAKE_UUID}/start`,
|
|
undefined,
|
|
{
|
|
headers: bearerHeaders(),
|
|
},
|
|
);
|
|
// FAKE_UUID is well-formed; the campaign lookup misses → 404.
|
|
expect(res.status).toBe(404);
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: advertising / creatives", () => {
|
|
test("GET /api/v1/advertising/creatives/:id rejects unauthenticated", async () => {
|
|
const res = await api.get(`/api/v1/advertising/creatives/${FAKE_UUID}`);
|
|
expectAuthGate(res.status, "GET advertising/creatives/:id");
|
|
});
|
|
|
|
test("PATCH /api/v1/advertising/creatives/:id rejects invalid body with 400", async () => {
|
|
const res = await api.patch(
|
|
`/api/v1/advertising/creatives/${FAKE_UUID}`,
|
|
{ media: [{ url: "not-a-url" }] },
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test("DELETE /api/v1/advertising/creatives/:id rejects unauthenticated", async () => {
|
|
const res = await api.delete(`/api/v1/advertising/creatives/${FAKE_UUID}`);
|
|
expectAuthGate(res.status, "DELETE advertising/creatives/:id");
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: training / vertex tune Worker boundary", () => {
|
|
test("POST /api/training/vertex/tune rejects unauthenticated", async () => {
|
|
const res = await api.post("/api/training/vertex/tune", {
|
|
datasetUri: "gs://demo",
|
|
});
|
|
expectAuthGate(res.status, "POST training/vertex/tune");
|
|
});
|
|
|
|
test("POST /api/training/vertex/tune returns 501 (node:fs blocker)", async () => {
|
|
const res = await api.post(
|
|
"/api/training/vertex/tune",
|
|
{ datasetUri: "gs://demo" },
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
// Worker boundary stub (node:fs blocker) — exactly 501 until migrated.
|
|
expect(res.status).toBe(501);
|
|
const body = (await res.json()) as { error?: string; reason?: string };
|
|
expect(body.error).toBe("not_yet_migrated");
|
|
});
|
|
|
|
test("GET /api/training/vertex/tune rejects unauthenticated", async () => {
|
|
const res = await api.get("/api/training/vertex/tune");
|
|
expectAuthGate(res.status, "GET training/vertex/tune");
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: session-cookie sanity check", () => {
|
|
test("can exchange API key for session cookie (sanity that base harness works)", async () => {
|
|
const freshCookie = await exchangeApiKeyForSession();
|
|
expect(freshCookie).toMatch(/^[^=]+=.+/);
|
|
});
|
|
});
|
|
|
|
describeE2E("Group E: admin / docker control-plane forwarding", () => {
|
|
test("POST /api/v1/admin/docker-nodes/:nodeId/health-check forwards to the control plane", async () => {
|
|
const unauthed = await api.post(
|
|
`/api/v1/admin/docker-nodes/${FAKE_UUID}/health-check`,
|
|
);
|
|
expectAuthGate(
|
|
unauthed.status,
|
|
"POST docker-nodes/:nodeId/health-check (unauth)",
|
|
);
|
|
|
|
const authed = await api.post(
|
|
`/api/v1/admin/docker-nodes/${FAKE_UUID}/health-check`,
|
|
undefined,
|
|
{ headers: bearerHeaders() },
|
|
);
|
|
expect(authed.status).toBe(503);
|
|
const body = (await authed.json()) as { code?: string };
|
|
expect([
|
|
"CONTAINER_CONTROL_PLANE_NOT_CONFIGURED",
|
|
"CONTAINER_CONTROL_PLANE_UNREACHABLE",
|
|
]).toContain(body.code ?? "");
|
|
});
|
|
});
|