fix(jd): publish Guanghu router runtime source
This commit is contained in:
parent
e82a377b58
commit
e2bb573a58
14 changed files with 1492 additions and 73 deletions
|
|
@ -7,6 +7,10 @@ const fs = require("node:fs");
|
|||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const { createApp } = require("./server");
|
||||
const {
|
||||
canonicalApproval,
|
||||
canonicalConnect,
|
||||
} = require("./guanghu-router");
|
||||
|
||||
async function withServer(run, extra = {}) {
|
||||
const mail = [];
|
||||
|
|
@ -22,7 +26,49 @@ async function withServer(run, extra = {}) {
|
|||
});
|
||||
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 { await new Promise((resolve) => app.close(resolve)); }
|
||||
try {
|
||||
await run({ base, mail });
|
||||
} finally {
|
||||
app.closeAllConnections?.();
|
||||
await new Promise((resolve) => app.close(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
function ownerForm(email = "owner@example.invalid", headers = {}) {
|
||||
return {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded", ...headers },
|
||||
body: new URLSearchParams({ email }).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
function routerDevice() {
|
||||
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
||||
const publicJwk = publicKey.export({ format: "jwk" });
|
||||
return {
|
||||
device: {
|
||||
device_id: "HL-BS-MAC-001",
|
||||
owner_id: "ICE-GL∞",
|
||||
label: "冰朔的 HoloLake",
|
||||
public_key: publicJwk.x,
|
||||
enabled: true,
|
||||
},
|
||||
privateKey,
|
||||
};
|
||||
}
|
||||
|
||||
async function readSseEvent(reader) {
|
||||
const decoder = new TextDecoder();
|
||||
let text = "";
|
||||
while (!text.includes("\n\n")) {
|
||||
const chunk = await reader.read();
|
||||
if (chunk.done) throw new Error("光湖路由连接在回执抵达前关闭");
|
||||
text += decoder.decode(chunk.value, { stream: true });
|
||||
}
|
||||
const block = text.slice(0, text.indexOf("\n\n"));
|
||||
const data = block.split("\n").find(line => line.startsWith("data:"));
|
||||
if (!data) throw new Error(`光湖路由回执缺少 data: ${block}`);
|
||||
return JSON.parse(data.slice(5).trim());
|
||||
}
|
||||
|
||||
test("work order sends an opaque approval link and can be claimed once", async () => {
|
||||
|
|
@ -84,14 +130,21 @@ test("cross-device request is powerless until the owner asks for an email and ap
|
|||
const handoffPage = await fetch(`${base}${requestPath}`);
|
||||
assert.equal(handoffPage.status, 200);
|
||||
assert.match(await handoffPage.text(), /无权限申请单/);
|
||||
assert.match(await (await fetch(`${base}${requestPath}`)).text(), /type="email"/);
|
||||
|
||||
const prematureClaim = await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } });
|
||||
assert.equal(prematureClaim.status, 202);
|
||||
|
||||
const sendMail = await fetch(`${base}${requestPath}`, { method: "POST", headers: { "x-forwarded-for": "203.0.113.9" } });
|
||||
const wrongEmail = await fetch(`${base}${requestPath}`, ownerForm("someone-else@example.invalid", { "x-forwarded-for": "203.0.113.8" }));
|
||||
assert.equal(wrongEmail.status, 200);
|
||||
assert.equal(mail.length, 0);
|
||||
const wrongPage = await wrongEmail.text();
|
||||
assert.match(wrongPage, /如果信息匹配/);
|
||||
|
||||
const sendMail = await fetch(`${base}${requestPath}`, ownerForm("Owner@Example.Invalid", { "x-forwarded-for": "203.0.113.9" }));
|
||||
assert.equal(sendMail.status, 200);
|
||||
assert.equal(mail.length, 1);
|
||||
assert.match(await sendMail.text(), /请打开邮箱完成批准/);
|
||||
assert.equal(await sendMail.text(), wrongPage);
|
||||
|
||||
const approvalPath = new URL(mail[0].approvalUrl).pathname.replace("/authz", "");
|
||||
const approved = await fetch(`${base}${approvalPath}`, { method: "POST" });
|
||||
|
|
@ -102,7 +155,106 @@ test("cross-device request is powerless until the owner asks for an email and ap
|
|||
});
|
||||
});
|
||||
|
||||
test("declared Work/mobile instance can notify the registered owner without a local credential", async () => {
|
||||
test("an online HoloLake receives and approves a workorder through the Guanghu Router", async () => {
|
||||
const { device, privateKey } = routerDevice();
|
||||
await withServer(async ({ base, mail }) => {
|
||||
const challengeResponse = await fetch(`${base}/api/guanghu-router/challenge`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ device_id: device.device_id }),
|
||||
});
|
||||
assert.equal(challengeResponse.status, 200);
|
||||
const challenge = await challengeResponse.json();
|
||||
const clientTimestamp = challenge.server_time;
|
||||
const connectSignature = crypto.sign(
|
||||
null,
|
||||
Buffer.from(canonicalConnect({
|
||||
deviceId: device.device_id,
|
||||
challengeId: challenge.challenge_id,
|
||||
nonce: challenge.nonce,
|
||||
clientTimestamp,
|
||||
})),
|
||||
privateKey,
|
||||
).toString("base64url");
|
||||
const connectResponse = await fetch(`${base}/api/guanghu-router/connect`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
device_id: device.device_id,
|
||||
challenge_id: challenge.challenge_id,
|
||||
client_timestamp: clientTimestamp,
|
||||
signature: connectSignature,
|
||||
}),
|
||||
});
|
||||
assert.equal(connectResponse.status, 200);
|
||||
const connection = await connectResponse.json();
|
||||
|
||||
const controller = new AbortController();
|
||||
const streamResponse = await fetch(`${base}/api/guanghu-router/stream`, {
|
||||
headers: { authorization: `Bearer ${connection.route_token}` },
|
||||
signal: controller.signal,
|
||||
});
|
||||
assert.equal(streamResponse.status, 200);
|
||||
const reader = streamResponse.body.getReader();
|
||||
const connected = await readSseEvent(reader);
|
||||
assert.equal(connected.type, "router.connected");
|
||||
assert.equal(connected.receipt.state, "online");
|
||||
assert.equal(connected.receipt.node_id, "JD-FD-PRIMARY");
|
||||
|
||||
const requested = await fetch(`${base}/api/public/workorders`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
persona_id: "ICE-GL-ZY001",
|
||||
persona_name: "铸渊",
|
||||
target: "JD-FD-PRIMARY",
|
||||
scope: "server-login",
|
||||
action: "read-navigation-map",
|
||||
}),
|
||||
});
|
||||
const order = await requested.json();
|
||||
assert.equal(order.status, "waiting_for_broadcast_console");
|
||||
assert.equal(order.delivery.channel, "guanghu_router");
|
||||
assert.equal(mail.length, 0);
|
||||
|
||||
const card = await readSseEvent(reader);
|
||||
assert.equal(card.type, "authorization.requested");
|
||||
assert.equal(card.workorder.id, order.workorder_id);
|
||||
const approvalSignature = crypto.sign(
|
||||
null,
|
||||
Buffer.from(canonicalApproval({
|
||||
deviceId: device.device_id,
|
||||
workorderId: order.workorder_id,
|
||||
digest: card.digest,
|
||||
})),
|
||||
privateKey,
|
||||
).toString("base64url");
|
||||
const approved = await fetch(
|
||||
`${base}/api/guanghu-router/authorizations/${order.workorder_id}/approve`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
device_id: device.device_id,
|
||||
signature: approvalSignature,
|
||||
}),
|
||||
},
|
||||
);
|
||||
assert.equal(approved.status, 200);
|
||||
assert.equal((await approved.json()).receipt.state, "approved");
|
||||
|
||||
const claimed = await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${order.claim_token}` },
|
||||
});
|
||||
assert.equal(claimed.status, 200);
|
||||
assert.equal((await claimed.json()).expires_in, 3 * 60 * 60);
|
||||
|
||||
controller.abort();
|
||||
}, { devices: [device] });
|
||||
});
|
||||
|
||||
test("declared Work/mobile instance cannot auto-email and must hand off to the owner page", async () => {
|
||||
await withServer(async ({ base, mail }) => {
|
||||
const requested = await fetch(`${base}/api/public/workorders`, {
|
||||
method: "POST", headers: { "content-type": "application/json" },
|
||||
|
|
@ -110,10 +262,11 @@ test("declared Work/mobile instance can notify the registered owner without a lo
|
|||
});
|
||||
assert.equal(requested.status, 201);
|
||||
const order = await requested.json();
|
||||
assert.equal(order.status, "waiting_for_owner");
|
||||
assert.equal(order.email_status, "sent");
|
||||
assert.equal(mail.length, 1);
|
||||
assert.match(mail[0].approvalUrl, /\/approve\//);
|
||||
assert.equal(order.status, "waiting_for_owner_handoff");
|
||||
assert.equal(order.email_status, "owner_input_required");
|
||||
assert.equal(order.public_auto_email, false);
|
||||
assert.equal(mail.length, 0);
|
||||
assert.match(order.request_url, /\/request\//);
|
||||
}, { actions: { "server-ops": ["read-navigation-map", "dispatch-approved-deployment"] } });
|
||||
});
|
||||
|
||||
|
|
@ -164,6 +317,153 @@ test("one server-ops approval exposes the full registered three-hour operation s
|
|||
}
|
||||
});
|
||||
|
||||
test("registered repositories resolve and an approved session can upload one bounded bundle", async () => {
|
||||
const uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), "guanghu-router-upload-"));
|
||||
const received = [];
|
||||
const registry = {
|
||||
"bingshuo/hololake-platform": {
|
||||
branches: ["main", "feat/hldp-runtime-browser"],
|
||||
remote: "file:///srv/forgejo/hololake-platform.git",
|
||||
source_urls: [
|
||||
"https://guanghulab.com/fifth-domain/bingshuo/hololake-platform.git",
|
||||
],
|
||||
},
|
||||
};
|
||||
const mapGate = {
|
||||
read: target => ({ hash: `map-${target}`, data: { node_id: target } }),
|
||||
ack: () => ({ ok: true }),
|
||||
verify: () => ({ ok: true }),
|
||||
};
|
||||
try {
|
||||
await withServer(async ({ base, mail }) => {
|
||||
const registered = await fetch(`${base}/api/repositories/resolve`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
remote_url: "https://guanghulab.com/fifth-domain/bingshuo/hololake-platform.git",
|
||||
}),
|
||||
});
|
||||
assert.equal(registered.status, 200);
|
||||
assert.equal((await registered.json()).repo, "bingshuo/hololake-platform");
|
||||
|
||||
const unregistered = await fetch(`${base}/api/repositories/resolve`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ remote_url: "" }),
|
||||
});
|
||||
assert.equal(unregistered.status, 404);
|
||||
assert.equal((await unregistered.json()).diagnostic_code, "local_repository_remote_missing");
|
||||
|
||||
const requested = await fetch(`${base}/api/public/workorders`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
persona_id: "ICE-GL-ZY001",
|
||||
persona_name: "铸渊",
|
||||
target: "JD-FD-PRIMARY",
|
||||
scope: "repo-push",
|
||||
action: "push-repository",
|
||||
resource: "bingshuo/hololake-platform@feat/hldp-runtime-browser",
|
||||
}),
|
||||
});
|
||||
const order = await requested.json();
|
||||
const requestPath = new URL(order.request_url).pathname.replace("/authz", "");
|
||||
await fetch(`${base}${requestPath}`, ownerForm());
|
||||
const approvalPath = new URL(mail[0].approvalUrl).pathname.replace("/authz", "");
|
||||
await fetch(`${base}${approvalPath}`, { method: "POST" });
|
||||
const session = await (await fetch(
|
||||
`${base}/api/workorders/${order.workorder_id}/claim`,
|
||||
{ method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } },
|
||||
)).json();
|
||||
const common = {
|
||||
persona_id: "ICE-GL-ZY001",
|
||||
target: "JD-FD-PRIMARY",
|
||||
scope: "repo-push",
|
||||
};
|
||||
const map = await (await fetch(`${base}/api/navigation-map/read`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${session.session_token}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(common),
|
||||
})).json();
|
||||
await fetch(`${base}/api/navigation-map/ack`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${session.session_token}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ ...common, map_hash: map.map_hash }),
|
||||
});
|
||||
|
||||
const bundle = Buffer.from("test-bundle-payload");
|
||||
const uploadId = "11111111-1111-4111-8111-111111111111";
|
||||
const firstChunk = bundle.subarray(0, 5);
|
||||
const partial = await fetch(
|
||||
`${base}/api/repo-push/bundle?repo=bingshuo%2Fhololake-platform&branch=feat%2Fhldp-runtime-browser&expected_head=${"0".repeat(40)}&persona_id=ICE-GL-ZY001&target=JD-FD-PRIMARY&scope=repo-push`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
authorization: `Bearer ${session.session_token}`,
|
||||
"content-length": String(firstChunk.length),
|
||||
"content-type": "application/octet-stream",
|
||||
"x-guanghu-upload-id": uploadId,
|
||||
"x-guanghu-chunk-index": "0",
|
||||
"x-guanghu-chunk-count": "2",
|
||||
"x-guanghu-chunk-offset": "0",
|
||||
},
|
||||
body: firstChunk,
|
||||
},
|
||||
);
|
||||
assert.equal(partial.status, 202);
|
||||
assert.equal((await partial.json()).upload.received_bytes, firstChunk.length);
|
||||
const lastChunk = bundle.subarray(5);
|
||||
const uploaded = await fetch(
|
||||
`${base}/api/repo-push/bundle?repo=bingshuo%2Fhololake-platform&branch=feat%2Fhldp-runtime-browser&expected_head=${"0".repeat(40)}&persona_id=ICE-GL-ZY001&target=JD-FD-PRIMARY&scope=repo-push`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
authorization: `Bearer ${session.session_token}`,
|
||||
"content-length": String(lastChunk.length),
|
||||
"content-type": "application/octet-stream",
|
||||
"x-guanghu-upload-id": uploadId,
|
||||
"x-guanghu-chunk-index": "1",
|
||||
"x-guanghu-chunk-count": "2",
|
||||
"x-guanghu-chunk-offset": String(firstChunk.length),
|
||||
},
|
||||
body: lastChunk,
|
||||
},
|
||||
);
|
||||
assert.equal(uploaded.status, 200);
|
||||
const result = await uploaded.json();
|
||||
assert.equal(result.receipt.diagnostic_code, "repo_push_succeeded");
|
||||
assert.equal(received.length, 1);
|
||||
assert.equal(received[0].bundle_bytes, "test-bundle-payload");
|
||||
}, {
|
||||
mapGate,
|
||||
repoPushRegistry: registry,
|
||||
repoUploadDir: uploadDir,
|
||||
receiveRepoBundle: async request => {
|
||||
received.push({
|
||||
...request,
|
||||
bundle_bytes: fs.readFileSync(request.bundle_path, "utf8"),
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
repo: request.repo,
|
||||
branch: request.branch,
|
||||
commit_sha: "b".repeat(40),
|
||||
diagnostic_code: "repo_push_succeeded",
|
||||
verification_url: "https://example.invalid/commit/" + "b".repeat(40),
|
||||
};
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
fs.rmSync(uploadDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("mail handoff allows at least three requests per network and refresh does not consume quota", async () => {
|
||||
await withServer(async ({ base, mail }) => {
|
||||
const source = { "x-forwarded-for": "198.51.100.42" };
|
||||
|
|
@ -174,8 +474,8 @@ test("mail handoff allows at least three requests per network and refresh does n
|
|||
});
|
||||
const order = await requested.json();
|
||||
const requestPath = new URL(order.request_url).pathname.replace("/authz", "");
|
||||
assert.equal((await fetch(`${base}${requestPath}`, { method: "POST", headers: source })).status, 200);
|
||||
assert.equal((await fetch(`${base}${requestPath}`, { method: "POST", headers: source })).status, 200);
|
||||
assert.equal((await fetch(`${base}${requestPath}`, ownerForm("owner@example.invalid", source))).status, 200);
|
||||
assert.equal((await fetch(`${base}${requestPath}`, ownerForm("owner@example.invalid", source))).status, 200);
|
||||
}
|
||||
assert.equal(mail.length, 3);
|
||||
const fourth = await fetch(`${base}/api/public/workorders`, {
|
||||
|
|
@ -184,7 +484,7 @@ test("mail handoff allows at least three requests per network and refresh does n
|
|||
});
|
||||
const fourthOrder = await fourth.json();
|
||||
const fourthPath = new URL(fourthOrder.request_url).pathname.replace("/authz", "");
|
||||
assert.equal((await fetch(`${base}${fourthPath}`, { method: "POST", headers: source })).status, 429);
|
||||
assert.equal((await fetch(`${base}${fourthPath}`, ownerForm("owner@example.invalid", source))).status, 429);
|
||||
}, { publicMailLimit: 1 });
|
||||
});
|
||||
|
||||
|
|
@ -222,7 +522,7 @@ test("active session renewal preserves target and scope and extends the map ackn
|
|||
await withServer(async ({ base, mail }) => {
|
||||
const requested = await fetch(`${base}/api/public/workorders`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map" }) });
|
||||
const order = await requested.json();
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, ownerForm());
|
||||
await fetch(`${base}${new URL(mail[0].approvalUrl).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
const session = await (await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } })).json();
|
||||
const common = { persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login" };
|
||||
|
|
@ -247,7 +547,7 @@ test("registered action bridge executes only after session and map checks", asyn
|
|||
const requested = await fetch(`${base}/api/public/workorders`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map" }) });
|
||||
const order = await requested.json();
|
||||
const requestPath = new URL(order.request_url).pathname.replace("/authz", "");
|
||||
await fetch(`${base}${requestPath}`, { method: "POST" });
|
||||
await fetch(`${base}${requestPath}`, ownerForm());
|
||||
const approvalPath = new URL(mail[0].approvalUrl).pathname.replace("/authz", "");
|
||||
await fetch(`${base}${approvalPath}`, { method: "POST" });
|
||||
const claimed = await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } });
|
||||
|
|
@ -281,7 +581,7 @@ test("failed server actions return a durable diagnosis instead of making a perso
|
|||
await withServer(async ({ base, mail }) => {
|
||||
const requested = await fetch(`${base}/api/public/workorders`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map" }) });
|
||||
const order = await requested.json();
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, ownerForm());
|
||||
await fetch(`${base}${new URL(mail[0].approvalUrl).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
const session = await (await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } })).json();
|
||||
const common = { persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login" };
|
||||
|
|
@ -306,7 +606,7 @@ test("deployment is dispatched only by an explicit approved second signal", asyn
|
|||
await withServer(async ({ base, mail }) => {
|
||||
const requested = await fetch(`${base}/api/public/workorders`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-ops", action: "dispatch-approved-deployment", resource }) });
|
||||
const order = await requested.json();
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, ownerForm());
|
||||
await fetch(`${base}${new URL(mail[0].approvalUrl).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
const session = await (await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } })).json();
|
||||
const common = { persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-ops" };
|
||||
|
|
@ -349,7 +649,7 @@ test("architecture provision request is bound to one immutable manifest and comm
|
|||
assert.equal((await missing.json()).error, "immutable_architecture_resource_required");
|
||||
const requested = await fetch(`${base}/api/public/workorders`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-ops", action: "provision-approved-architecture", resource }) });
|
||||
const order = await requested.json();
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, ownerForm());
|
||||
assert.equal(mail[0].order.resource, resource);
|
||||
const approvalPath = new URL(mail[0].approvalUrl).pathname.replace("/authz", "");
|
||||
assert.match(await (await fetch(`${base}${approvalPath}`)).text(), new RegExp(resource));
|
||||
|
|
@ -388,7 +688,7 @@ test("server policy selects a member mailbox by persona target and scope", async
|
|||
});
|
||||
const order = await requested.json();
|
||||
const requestPath = new URL(order.request_url).pathname.replace("/authz", "");
|
||||
assert.equal((await fetch(`${base}${requestPath}`, { method: "POST" })).status, 200);
|
||||
assert.equal((await fetch(`${base}${requestPath}`, ownerForm("awen@example.invalid"))).status, 200);
|
||||
assert.equal(mail[0].to, "awen@example.invalid");
|
||||
}, { approvers: [
|
||||
{ id: "owner", email: "owner@example.invalid", default: true, persona_ids: ["ICE-GL-ZY001"], targets: ["*"], scopes: ["*"] },
|
||||
|
|
|
|||
Loading…
Reference in a new issue