2026-08-02 21:49:53 +08:00
|
|
|
"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"),
|
|
|
|
|
}),
|
2026-08-03 22:27:09 +08:00
|
|
|
writePage: request => {
|
|
|
|
|
if (request.path.includes("本地密钥")) {
|
|
|
|
|
throw new Error("knowledge_secret_page_forbidden");
|
|
|
|
|
}
|
|
|
|
|
if (request.path === "notes/conflict.md") {
|
|
|
|
|
throw new Error("knowledge_commit_conflict");
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
schema: "guanghu.hololake-knowledge-write-receipt/v1",
|
|
|
|
|
repository: "bingshuo/hololake-knowledge-base",
|
|
|
|
|
path: request.path,
|
|
|
|
|
base_commit: request.baseCommit,
|
|
|
|
|
commit: "c".repeat(40),
|
|
|
|
|
content_sha256: crypto
|
|
|
|
|
.createHash("sha256")
|
|
|
|
|
.update(request.content)
|
|
|
|
|
.digest("hex"),
|
|
|
|
|
};
|
|
|
|
|
},
|
2026-08-02 21:49:53 +08:00
|
|
|
};
|
|
|
|
|
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");
|
|
|
|
|
|
2026-08-03 22:27:09 +08:00
|
|
|
const pageWrite = await fetch(`${base}/api/hololake/knowledge/page`, {
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: { ...headers, "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
path: "个人知识/手机页面.md",
|
|
|
|
|
content: "# 手机页面\n",
|
|
|
|
|
base_commit: "a".repeat(40),
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(pageWrite.status, 200);
|
|
|
|
|
const writeReceipt = await pageWrite.json();
|
|
|
|
|
assert.equal(writeReceipt.path, "个人知识/手机页面.md");
|
|
|
|
|
assert.equal(writeReceipt.commit, "c".repeat(40));
|
|
|
|
|
assert.doesNotMatch(JSON.stringify(writeReceipt), /# 手机页面/);
|
|
|
|
|
|
2026-08-02 21:49:53 +08:00
|
|
|
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,
|
|
|
|
|
);
|
2026-08-03 22:27:09 +08:00
|
|
|
assert.equal(
|
|
|
|
|
(await fetch(`${base}/api/hololake/knowledge/page`, {
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
path: "notes/no-session.md",
|
|
|
|
|
content: "# no\n",
|
|
|
|
|
base_commit: "a".repeat(40),
|
|
|
|
|
}),
|
|
|
|
|
})).status,
|
|
|
|
|
401,
|
|
|
|
|
);
|
2026-08-02 21:49:53 +08:00
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-08-03 22:27:09 +08:00
|
|
|
|
|
|
|
|
test("knowledge write endpoint maps conflicts and validation failures without echoing content", 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,
|
|
|
|
|
"content-type": "application/json",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const forbidden = await fetch(`${base}/api/hololake/knowledge/page`, {
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers,
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
path: "本地密钥/openai.md",
|
|
|
|
|
content: "super-secret-value",
|
|
|
|
|
base_commit: "a".repeat(40),
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(forbidden.status, 400);
|
|
|
|
|
assert.doesNotMatch(await forbidden.text(), /super-secret-value/);
|
|
|
|
|
|
|
|
|
|
const conflict = await fetch(`${base}/api/hololake/knowledge/page`, {
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers,
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
path: "notes/conflict.md",
|
|
|
|
|
content: "# stale\n",
|
|
|
|
|
base_commit: "a".repeat(40),
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(conflict.status, 409);
|
|
|
|
|
assert.equal((await conflict.json()).error, "knowledge_commit_conflict");
|
|
|
|
|
});
|
|
|
|
|
});
|