"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const crypto = require("node:crypto"); const { HoloLakeSessionManager, } = require("./hololake-session"); const { createApp } = require("./server"); async function withServer(run) { const mail = []; const sessionManager = new HoloLakeSessionManager({ registeredEmails: ["owner@example.invalid"], pepper: "test-only-pepper-with-enough-entropy", stateFile: "", sendEmail: async message => { mail.push(message); return true; }, }); const knowledgeProvider = { manifest: () => ({ schema: "guanghu.hololake-knowledge-manifest/v1", repository: "bingshuo/hololake-knowledge-base", ref: "refs/heads/main", commit: "a".repeat(40), committed_at: 1_800_000_000, archive_url: `/api/hololake/knowledge/archive?commit=${"a".repeat(40)}`, }), archive: commit => ({ schema: "guanghu.hololake-knowledge-archive/v1", repository: "bingshuo/hololake-knowledge-base", commit, sha256: "b".repeat(64), content_type: "application/zip", body: Buffer.from("PK-test-archive"), }), }; const aiGateway = { catalog: () => ({ schema: "guanghu.hololake-ai-catalog/v1", providers: [{ id: "default", name: "HoloLake", models: ["gpt-test"] }], }), execute: async body => ({ ok: true, response: { choices: [{ message: { role: "assistant", content: body.messages[0].content, }, }], }, receipt: { schema: "guanghu.hololake-ai-receipt/v1", state: "executed", provider: body.provider, model: body.model, }, }), }; const app = createApp({ requestToken: "request-only-secret", ownerEmail: "owner@example.invalid", publicBaseUrl: "https://example.invalid/authz", stateFile: "", sendEmail: async () => true, hololakeSessionManager: sessionManager, hololakeKnowledgeProvider: knowledgeProvider, hololakeAiGateway: aiGateway, }); await new Promise(resolve => app.listen(0, "127.0.0.1", resolve)); const base = `http://127.0.0.1:${app.address().port}`; try { await run({ base, mail }); } finally { app.closeAllConnections?.(); await new Promise(resolve => app.close(resolve)); } } async function login(base, mail, deviceId = "ios-device-001") { const request = await fetch(`${base}/api/hololake/session/email/request`, { method: "POST", headers: { "content-type": "application/json", "x-hololake-device-id": deviceId, }, body: JSON.stringify({ email: "owner@example.invalid", device_id: deviceId, }), }); assert.equal(request.status, 202); const requested = await request.json(); const code = mail[0].text.match(/\b\d{6}\b/)[0]; const verifiedResponse = await fetch( `${base}/api/hololake/session/email/verify`, { method: "POST", headers: { "content-type": "application/json", "x-hololake-device-id": deviceId, }, body: JSON.stringify({ request_id: requested.request_id, code, device_id: deviceId, }), }, ); assert.equal(verifiedResponse.status, 200); return (await verifiedResponse.json()).session_token; } test("HoloLake session API is non-enumerating and rejects invalid verification", async () => { await withServer(async ({ base, mail }) => { const unknown = await fetch( `${base}/api/hololake/session/email/request`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "nobody@example.invalid", device_id: "ios-device-001", }), }, ); assert.equal(unknown.status, 202); assert.equal(mail.length, 0); assert.equal((await unknown.json()).accepted, true); const invalid = await fetch( `${base}/api/hololake/session/email/verify`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ request_id: crypto.randomUUID(), code: "000000", device_id: "ios-device-001", }), }, ); assert.equal(invalid.status, 403); assert.equal((await invalid.json()).error, "invalid_or_expired_code"); }); }); test("authenticated device can read fixed knowledge, use AI proxy, inspect and revoke session", async () => { await withServer(async ({ base, mail }) => { const deviceId = "ios-device-001"; const token = await login(base, mail, deviceId); const headers = { authorization: `Bearer ${token}`, "x-hololake-device-id": deviceId, }; const session = await fetch(`${base}/api/hololake/session`, { headers }); assert.equal(session.status, 200); assert.equal((await session.json()).session.device_id, deviceId); const manifest = await fetch( `${base}/api/hololake/knowledge/manifest`, { headers }, ); assert.equal(manifest.status, 200); assert.equal((await manifest.json()).commit, "a".repeat(40)); const archive = await fetch( `${base}/api/hololake/knowledge/archive?commit=${"a".repeat(40)}`, { headers }, ); assert.equal(archive.status, 200); assert.equal(archive.headers.get("x-hololake-commit"), "a".repeat(40)); assert.equal(archive.headers.get("x-content-sha256"), "b".repeat(64)); assert.equal(Buffer.from(await archive.arrayBuffer()).toString(), "PK-test-archive"); const ai = await fetch(`${base}/api/hololake/ai/execute`, { method: "POST", headers: { ...headers, "content-type": "application/json" }, body: JSON.stringify({ provider: "default", model: "gpt-test", messages: [{ role: "user", content: "hello" }], }), }); assert.equal(ai.status, 200); assert.equal( (await ai.json()).response.choices[0].message.content, "hello", ); const catalog = await fetch(`${base}/api/hololake/ai/catalog`, { headers }); assert.equal(catalog.status, 200); assert.deepEqual((await catalog.json()).providers[0].models, ["gpt-test"]); const logout = await fetch(`${base}/api/hololake/session`, { method: "DELETE", headers, }); assert.equal(logout.status, 200); assert.equal( (await fetch(`${base}/api/hololake/session`, { headers })).status, 401, ); }); }); test("knowledge and AI endpoints require the session and matching device", async () => { await withServer(async ({ base, mail }) => { assert.equal( (await fetch(`${base}/api/hololake/knowledge/manifest`)).status, 401, ); assert.equal( (await fetch(`${base}/api/hololake/ai/execute`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}", })).status, 401, ); const token = await login(base, mail); assert.equal( (await fetch(`${base}/api/hololake/knowledge/manifest`, { headers: { authorization: `Bearer ${token}`, "x-hololake-device-id": "ios-device-002", }, })).status, 403, ); }); });