2e3e5e5e59
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.8 KiB
JavaScript
101 lines
3.8 KiB
JavaScript
"use strict"
|
|
|
|
const test = require("node:test")
|
|
const assert = require("node:assert/strict")
|
|
|
|
const BASE_URL = process.env.HERMES_E2E_URL || "http://127.0.0.1:7843"
|
|
const ADMIN_USERNAME = process.env.HERMES_ADMIN_USERNAME || "admin"
|
|
const ADMIN_PASSWORD = process.env.HERMES_ADMIN_PASSWORD || ""
|
|
const PRE_API_URL = process.env.HERMES_PRE_E2E_URL || "http://127.0.0.1:8645"
|
|
const POST_API_URL = process.env.HERMES_POST_E2E_URL || "http://127.0.0.1:8646"
|
|
|
|
test("e2e smoke test — full flow", async (t) => {
|
|
if (!process.env.RUN_E2E) {
|
|
t.skip("RUN_E2E not set — skipping e2e smoke test")
|
|
return
|
|
}
|
|
|
|
// Step 1: Login to control plane
|
|
const loginRes = await fetch(`${BASE_URL}/api/admin/login`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username: ADMIN_USERNAME, password: ADMIN_PASSWORD })
|
|
})
|
|
assert.equal(loginRes.status, 200, "login should succeed")
|
|
|
|
const cookieHeader = loginRes.headers.get("set-cookie")
|
|
assert.ok(cookieHeader, "should set session cookie")
|
|
const sessionCookie = cookieHeader.split(";")[0]
|
|
|
|
// Step 2: Create a pre-only API user
|
|
const createRes = await fetch(`${BASE_URL}/api/admin/api-users`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: sessionCookie },
|
|
body: JSON.stringify({
|
|
displayName: "E2E Test User",
|
|
allowPre: true,
|
|
allowPost: false,
|
|
requestsPerMinute: 60,
|
|
monthlyTokenLimit: 1000000
|
|
})
|
|
})
|
|
assert.equal(createRes.status, 200, "create API user should succeed")
|
|
const { user, plaintextKey } = await createRes.json()
|
|
assert.ok(plaintextKey.startsWith("hms_"), "key should start with hms_")
|
|
|
|
// Step 3: Call pre API successfully
|
|
const preRes = await fetch(`${PRE_API_URL}/v1/chat/completions`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${plaintextKey}`
|
|
},
|
|
body: JSON.stringify({ model: "test", messages: [{ role: "user", content: "hello" }] })
|
|
})
|
|
assert.ok(preRes.status < 500, "pre API call should not return 5xx (upstream may fail but gateway should respond)")
|
|
|
|
// Step 4: Call post API and receive 403
|
|
const postRes = await fetch(`${POST_API_URL}/v1/chat/completions`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${plaintextKey}`
|
|
},
|
|
body: JSON.stringify({ model: "test", messages: [{ role: "user", content: "hello" }] })
|
|
})
|
|
assert.equal(postRes.status, 403, "pre-only key should get 403 on post gateway")
|
|
|
|
// Step 5: Rotate the key
|
|
const rotateRes = await fetch(`${BASE_URL}/api/admin/api-users/${user.id}/rotate`, {
|
|
method: "POST",
|
|
headers: { Cookie: sessionCookie }
|
|
})
|
|
assert.equal(rotateRes.status, 200, "rotate should succeed")
|
|
const { plaintextKey: newKey } = await rotateRes.json()
|
|
|
|
// Old key should now get 410
|
|
const oldKeyRes = await fetch(`${PRE_API_URL}/v1/chat/completions`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${plaintextKey}`
|
|
},
|
|
body: JSON.stringify({ model: "test", messages: [] })
|
|
})
|
|
assert.equal(oldKeyRes.status, 410, "old key should return 410 after rotation")
|
|
|
|
// Step 6: Download JSONL and verify
|
|
const logsRes = await fetch(
|
|
`${BASE_URL}/api/admin/logs/download?api_user_id=${user.id}`,
|
|
{ headers: { Cookie: sessionCookie } }
|
|
)
|
|
assert.equal(logsRes.status, 200, "logs download should succeed")
|
|
const contentType = logsRes.headers.get("content-type")
|
|
assert.match(contentType, /ndjson/, "response should be ndjson")
|
|
|
|
const body = await logsRes.text()
|
|
// There should be at least one JSONL line from the pre API call
|
|
const lines = body.trim().split("\n").filter(Boolean)
|
|
assert.ok(lines.length >= 1, "should have at least one log entry")
|
|
})
|