@7365admin1/core 3.86.0 → 3.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/index.d.ts +87 -5
- package/dist/index.js +2724 -2427
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1447 -1152
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/demo-adapter.test.mjs +33 -0
- package/test/e2e/harness.mjs +3 -1
- package/test/e2e/robot-scope.e2e.test.mjs +70 -0
- package/test/robot-adapter.test.mjs +9 -0
- package/test/robot-fault.test.mjs +39 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7365admin1/core",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.87.0",
|
|
5
5
|
"author": "7365admin1",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build": "tsup src/index.ts --format cjs,esm --dts && shx cp -r src/public dist && shx cp -r src/utils/handlebars dist",
|
|
14
14
|
"release": "yarn run build && changeset publish",
|
|
15
15
|
"lint": "tsc",
|
|
16
|
-
"test": "tsup src/config.ts src/utils/building-map.util.ts src/utils/robot-cloud.util.ts src/utils/robot-adapter.util.ts src/utils/png-encoder.util.ts src/services/demo.adapter.ts src/utils/robot-tracking.util.ts src/utils/robot-apps.util.ts src/utils/robot-permission.util.ts src/utils/lift-contract.util.ts src/utils/anpr-revoke.util.ts src/utils/anpr-plate.util.ts src/utils/vehicle-request.util.ts src/utils/camera-view.util.ts src/utils/camera-bulk-import.util.ts src/utils/dashboard-metrics.util.ts src/utils/camera-capability.util.ts src/utils/camera-playback.util.ts src/services/camera-device-http.service.ts src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts src/utils/site-name.util.ts src/utils/service-provider-invite.util.ts src/utils/notification-category.util.ts src/utils/hid-card.util.ts src/utils/hid-json.util.ts src/utils/hid-access.util.ts src/models/customer-site.model.ts src/models/site-camera.model.ts src/models/member.model.ts src/models/subscription.model.ts src/utils/session-id.util.ts src/utils/occurrence-query.util.ts src/utils/occurrence-book-create.util.ts src/utils/update-result.util.ts src/utils/hid-amico-authz.util.ts src/utils/promo-code-currency.util.ts src/utils/console-audit.util.ts src/utils/org-suspension.util.ts src/utils/session-revoke.util.ts src/models/hid-amico.model.ts src/models/patrol-email.model.ts src/utils/patrol-email-pdf.util.ts src/utils/visitor-status.util.ts src/utils/self-service-email.util.ts src/utils/hid-secret.util.ts src/models/visitor-invite.model.ts src/models/guard-invite.model.ts src/utils/site-timezone.util.ts src/utils/bulletin-service-apps.util.ts src/models/bulletin-board.model.ts src/utils/vehicle-import.util.ts src/utils/expired-vehicle-sweep.util.ts src/models/vehicle.model.ts src/controllers/chat.controller.ts src/utils/file-owner.util.ts src/utils/console-permission.util.ts src/utils/role-scope.util.ts src/utils/org-default-roles.util.ts src/utils/org-modules.util.ts src/utils/site-modules.util.ts src/utils/resident-app-modules.util.ts src/utils/resident-default-role.util.ts src/utils/invite-role-scope.util.ts src/models/personal-emergency-chain.model.ts src/utils/self-signup-files.util.ts src/models/person.model.ts src/utils/module-gate.util.ts src/utils/member-lookup.util.ts src/utils/notification-access.util.ts src/utils/module-access.util.ts src/services/module-gate.service.ts src/controllers/site.controller.ts src/controllers/organization.controller.ts src/controllers/member.controller.ts src/utils/org-owner-repair.util.ts src/utils/socket-session.util.ts src/utils/people-create-gate.util.ts src/utils/service-app-type.util.ts --format esm --out-dir test/.build --no-dts --silent && node --test test/*.test.mjs",
|
|
16
|
+
"test": "tsup src/config.ts src/utils/building-map.util.ts src/utils/robot-cloud.util.ts src/utils/robot-adapter.util.ts src/utils/png-encoder.util.ts src/services/demo.adapter.ts src/utils/robot-fault.util.ts src/utils/robot-tracking.util.ts src/utils/robot-apps.util.ts src/utils/robot-permission.util.ts src/utils/lift-contract.util.ts src/utils/anpr-revoke.util.ts src/utils/anpr-plate.util.ts src/utils/vehicle-request.util.ts src/utils/camera-view.util.ts src/utils/camera-bulk-import.util.ts src/utils/dashboard-metrics.util.ts src/utils/camera-capability.util.ts src/utils/camera-playback.util.ts src/services/camera-device-http.service.ts src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts src/utils/site-name.util.ts src/utils/service-provider-invite.util.ts src/utils/notification-category.util.ts src/utils/hid-card.util.ts src/utils/hid-json.util.ts src/utils/hid-access.util.ts src/models/customer-site.model.ts src/models/site-camera.model.ts src/models/member.model.ts src/models/subscription.model.ts src/utils/session-id.util.ts src/utils/occurrence-query.util.ts src/utils/occurrence-book-create.util.ts src/utils/update-result.util.ts src/utils/hid-amico-authz.util.ts src/utils/promo-code-currency.util.ts src/utils/console-audit.util.ts src/utils/org-suspension.util.ts src/utils/session-revoke.util.ts src/models/hid-amico.model.ts src/models/patrol-email.model.ts src/utils/patrol-email-pdf.util.ts src/utils/visitor-status.util.ts src/utils/self-service-email.util.ts src/utils/hid-secret.util.ts src/models/visitor-invite.model.ts src/models/guard-invite.model.ts src/utils/site-timezone.util.ts src/utils/bulletin-service-apps.util.ts src/models/bulletin-board.model.ts src/utils/vehicle-import.util.ts src/utils/expired-vehicle-sweep.util.ts src/models/vehicle.model.ts src/controllers/chat.controller.ts src/utils/file-owner.util.ts src/utils/console-permission.util.ts src/utils/role-scope.util.ts src/utils/org-default-roles.util.ts src/utils/org-modules.util.ts src/utils/site-modules.util.ts src/utils/resident-app-modules.util.ts src/utils/resident-default-role.util.ts src/utils/invite-role-scope.util.ts src/models/personal-emergency-chain.model.ts src/utils/self-signup-files.util.ts src/models/person.model.ts src/utils/module-gate.util.ts src/utils/member-lookup.util.ts src/utils/notification-access.util.ts src/utils/module-access.util.ts src/services/module-gate.service.ts src/controllers/site.controller.ts src/controllers/organization.controller.ts src/controllers/member.controller.ts src/utils/org-owner-repair.util.ts src/utils/socket-session.util.ts src/utils/people-create-gate.util.ts src/utils/service-app-type.util.ts --format esm --out-dir test/.build --no-dts --silent && node --test test/*.test.mjs",
|
|
17
17
|
"test:camera": "yarn test",
|
|
18
18
|
"test:e2e": "yarn build && node --test --test-concurrency=1 --test-timeout=600000 \"test/e2e/*.test.mjs\""
|
|
19
19
|
},
|
|
@@ -162,3 +162,36 @@ test("the encoder refuses an impossible image rather than emitting a broken one"
|
|
|
162
162
|
assert.throws(() => encodeGreyscalePng(new Uint8Array(4), 0, 4), /positive size/);
|
|
163
163
|
assert.throws(() => encodeGreyscalePng(new Uint8Array(2), 10, 10), /one byte per pixel/);
|
|
164
164
|
});
|
|
165
|
+
|
|
166
|
+
/* ------------------------------------------------ faults and spot cleaning */
|
|
167
|
+
|
|
168
|
+
import { demoFault, DEMO_FAULT_ROBOT } from "./.build/services/demo.adapter.mjs";
|
|
169
|
+
|
|
170
|
+
test("one demo robot faults two minutes in every thirty, keyed by when it started", async () => {
|
|
171
|
+
const windowStart = 30 * 60_000 * 1000;
|
|
172
|
+
const inside = await at(windowStart + 60_000).getRobot(DEMO_FAULT_ROBOT);
|
|
173
|
+
assert.equal(inside.mode, "MAINTENANCE");
|
|
174
|
+
assert.equal(inside.fault.code, "DEMO_BRUSH_JAM");
|
|
175
|
+
assert.equal(inside.fault.since, windowStart);
|
|
176
|
+
assert.match(inside.fault.message, /simulated/);
|
|
177
|
+
|
|
178
|
+
const after = await at(windowStart + 3 * 60_000).getRobot(DEMO_FAULT_ROBOT);
|
|
179
|
+
assert.equal(after.fault, null);
|
|
180
|
+
// Only the one robot, so a fleet screen is not a wall of red.
|
|
181
|
+
assert.equal(demoFault("DEMO-0001", windowStart), null);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("the demo brand supports spot cleaning, and parks the robot on the spot", async () => {
|
|
185
|
+
assert.equal(commandSupported(DEMO_CAPABILITIES, "spot"), true);
|
|
186
|
+
const t = 5 * 60_000; // outside any fault window, battery well above flat
|
|
187
|
+
const demo = at(t);
|
|
188
|
+
await demo.sendCommand("DEMO-0001", {
|
|
189
|
+
kind: "spot",
|
|
190
|
+
points: [{ x: 90, y: 50 }, { x: 110, y: 50 }, { x: 110, y: 70 }, { x: 90, y: 70 }],
|
|
191
|
+
});
|
|
192
|
+
const robot = await demo.getRobot("DEMO-0001");
|
|
193
|
+
assert.equal(robot.mode, "WORKING");
|
|
194
|
+
// Pixels to metres at 0.05 m/px, y flipped (180 px tall plan).
|
|
195
|
+
assert.ok(Math.abs(robot.position.x - 5) < 1e-9);
|
|
196
|
+
assert.ok(Math.abs(robot.position.y - (180 - 60) * 0.05) < 1e-9);
|
|
197
|
+
});
|
package/test/e2e/harness.mjs
CHANGED
|
@@ -874,10 +874,12 @@ function buildApp(nsu, core) {
|
|
|
874
874
|
// mirrors iservice365-API-core/src/routes/robot.route.ts
|
|
875
875
|
const robots = express.Router();
|
|
876
876
|
{
|
|
877
|
-
const { createRobot, getRobots } = core.useRobotController();
|
|
877
|
+
const { createRobot, getRobots, updateRobot, deleteRobot } = core.useRobotController();
|
|
878
878
|
|
|
879
879
|
robots.post("/", requireAuth, createRobot);
|
|
880
880
|
robots.get("/organization/:organization/site/:site", requireAuth, getRobots);
|
|
881
|
+
robots.patch("/:id([0-9a-fA-F]{24})", requireAuth, updateRobot);
|
|
882
|
+
robots.delete("/:id([0-9a-fA-F]{24})", requireAuth, deleteRobot);
|
|
881
883
|
}
|
|
882
884
|
|
|
883
885
|
// mirrors iservice365-API-core/src/routes/attendance-settings.route.ts
|
|
@@ -290,6 +290,73 @@ describe("robots and their credentials are scoped to their estate", { concurrenc
|
|
|
290
290
|
assert.deepEqual(stranger.body, insider.body);
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
+
// ---- edit and remove: the estate comes off the STORED robot ---------------
|
|
294
|
+
|
|
295
|
+
const seeded = async (name) => (await byName(name))._id;
|
|
296
|
+
const audits = async (action, target) =>
|
|
297
|
+
await h.db.collection("console-audit").find({ action, target: String(target) }).toArray();
|
|
298
|
+
|
|
299
|
+
record("14. an outsider cannot EDIT or REMOVE another estate's robot by quoting its id", async () => {
|
|
300
|
+
const robotB = await seeded("rb-seeded-b");
|
|
301
|
+
const edit = await h.api(`/robots/${robotB}`, {
|
|
302
|
+
method: "PATCH", sid: outsiderSid, body: { name: "rb-hijacked" },
|
|
303
|
+
});
|
|
304
|
+
const remove = await h.api(`/robots/${robotB}`, { method: "DELETE", sid: outsiderSid });
|
|
305
|
+
|
|
306
|
+
assert.equal(edit.status, 404);
|
|
307
|
+
assert.equal(remove.status, 404);
|
|
308
|
+
const stored = await h.db.collection(COLL).findOne({ _id: robotB });
|
|
309
|
+
assert.equal(stored.name, "rb-seeded-b");
|
|
310
|
+
assert.ok(!stored.deletedAt);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
record("15. the estate's own manager edits a robot, and it is audited without credentials", async () => {
|
|
314
|
+
const robotB = await seeded("rb-seeded-b");
|
|
315
|
+
const res = await h.api(`/robots/${robotB}`, {
|
|
316
|
+
method: "PATCH", sid: managerSid, body: { name: "rb-renamed-b", robotSn: "SN-1" },
|
|
317
|
+
});
|
|
318
|
+
assert.equal(res.status, 200);
|
|
319
|
+
|
|
320
|
+
const stored = await h.db.collection(COLL).findOne({ _id: robotB });
|
|
321
|
+
assert.equal(stored.name, "rb-renamed-b");
|
|
322
|
+
assert.equal(stored.robotSn, "SN-1");
|
|
323
|
+
|
|
324
|
+
const [row] = await audits("robot.updated", robotB);
|
|
325
|
+
assert.equal(row?.after?.name, "rb-renamed-b");
|
|
326
|
+
assert.equal(row?.before?.name, "rb-seeded-b");
|
|
327
|
+
assert.equal(JSON.stringify(row).includes("e2e-fake"), false);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
record("16. an empty edit is a 400", async () => {
|
|
331
|
+
const robotB = await seeded("rb-renamed-b");
|
|
332
|
+
const res = await h.api(`/robots/${robotB}`, { method: "PATCH", sid: managerSid, body: {} });
|
|
333
|
+
assert.equal(res.status, 400);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
record("17. a removed robot stops listing and answers as not found, and the row stays", async () => {
|
|
337
|
+
const robotB = await seeded("rb-renamed-b");
|
|
338
|
+
const remove = await h.api(`/robots/${robotB}`, { method: "DELETE", sid: managerSid });
|
|
339
|
+
assert.equal(remove.status, 200);
|
|
340
|
+
|
|
341
|
+
const list = await h.api(listUrl(id.orgB, id.siteB), { sid: managerSid });
|
|
342
|
+
assert.equal((list.body?.items ?? []).some((r) => r.name === "rb-renamed-b"), false);
|
|
343
|
+
|
|
344
|
+
const again = await h.api(`/robots/${robotB}`, { method: "DELETE", sid: managerSid });
|
|
345
|
+
assert.equal(again.status, 404);
|
|
346
|
+
|
|
347
|
+
const stored = await h.db.collection(COLL).findOne({ _id: robotB });
|
|
348
|
+
assert.ok(stored.deletedAt, "soft delete keeps the row");
|
|
349
|
+
assert.equal((await audits("robot.removed", robotB)).length, 1);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
record("18. creating a robot writes an audit row, without credentials", async () => {
|
|
353
|
+
const robot = await byName("rb-allowed");
|
|
354
|
+
const [row] = await audits("robot.added", robot._id);
|
|
355
|
+
assert.equal(row?.after?.name, "rb-allowed");
|
|
356
|
+
assert.equal(String(row?.org), String(id.orgB));
|
|
357
|
+
assert.equal(JSON.stringify(row).includes("e2e-fake"), false);
|
|
358
|
+
});
|
|
359
|
+
|
|
293
360
|
record("13. no session is still a 401 on both routes", async () => {
|
|
294
361
|
const list = await h.api(listUrl(id.orgB, id.siteB));
|
|
295
362
|
const create = await h.api("/robots", {
|
|
@@ -299,6 +366,9 @@ describe("robots and their credentials are scoped to their estate", { concurrenc
|
|
|
299
366
|
|
|
300
367
|
assert.equal(list.status, 401);
|
|
301
368
|
assert.equal(create.status, 401);
|
|
369
|
+
const robotA = await seeded("rb-seeded-a");
|
|
370
|
+
assert.equal((await h.api(`/robots/${robotA}`, { method: "PATCH", body: { name: "x" } })).status, 401);
|
|
371
|
+
assert.equal((await h.api(`/robots/${robotA}`, { method: "DELETE" })).status, 401);
|
|
302
372
|
});
|
|
303
373
|
});
|
|
304
374
|
|
|
@@ -129,6 +129,15 @@ test("each command maps to the capability it actually needs", () => {
|
|
|
129
129
|
assert.equal(commandSupported(caps("navigateToPoint"), "navigate"), true);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
test("a spot clean needs spotClean, not just startProcess - a click needs a verified frame", () => {
|
|
133
|
+
assert.equal(commandSupported(caps("startProcess"), "spot"), false);
|
|
134
|
+
assert.equal(commandSupported(caps("spotClean"), "spot"), true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("spotClean is ours, not Open-RMF's, and moves no brand's tier", () => {
|
|
138
|
+
assert.equal(robotTier(caps("readLocation", "spotClean")), robotTier(caps("readLocation")));
|
|
139
|
+
});
|
|
140
|
+
|
|
132
141
|
test("an unknown command kind is refused, not defaulted", () => {
|
|
133
142
|
for (const kind of ["selfDestruct", "", null, undefined, "constructor"]) {
|
|
134
143
|
assert.equal(commandSupported(ALL, kind), false, String(kind));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
claimFaultOnce,
|
|
6
|
+
robotFaultKey,
|
|
7
|
+
robotFaultNotifyEnabled,
|
|
8
|
+
robotFaultText,
|
|
9
|
+
} from "./.build/utils/robot-fault.util.mjs";
|
|
10
|
+
|
|
11
|
+
test("the same fault occurrence is claimed once per process", async () => {
|
|
12
|
+
const local = new Set();
|
|
13
|
+
const key = robotFaultKey("r1", { code: "JAM", since: 10 });
|
|
14
|
+
assert.equal(await claimFaultOnce(key, { local }), true);
|
|
15
|
+
assert.equal(await claimFaultOnce(key, { local }), false);
|
|
16
|
+
// A second occurrence of the same fault notifies again.
|
|
17
|
+
assert.equal(await claimFaultOnce(robotFaultKey("r1", { code: "JAM", since: 20 }), { local }), true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("another instance holding the claim suppresses it; an unreachable store does not", async () => {
|
|
21
|
+
assert.equal(await claimFaultOnce("k1", { local: new Set(), claimShared: async () => false }), false);
|
|
22
|
+
assert.equal(await claimFaultOnce("k2", { local: new Set(), claimShared: async () => null }), true);
|
|
23
|
+
assert.equal(
|
|
24
|
+
await claimFaultOnce("k3", { local: new Set(), claimShared: async () => { throw new Error("down"); } }),
|
|
25
|
+
true,
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("ROBOT_FAULT_NOTIFY=off silences it; anything else leaves it on", () => {
|
|
30
|
+
assert.equal(robotFaultNotifyEnabled({}), true);
|
|
31
|
+
assert.equal(robotFaultNotifyEnabled({ ROBOT_FAULT_NOTIFY: " OFF " }), false);
|
|
32
|
+
assert.equal(robotFaultNotifyEnabled({ ROBOT_FAULT_NOTIFY: "on" }), true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("the text names the robot and the fault", () => {
|
|
36
|
+
const { body } = robotFaultText("Cleaner 02", { code: "JAM", message: "Side brush jammed" });
|
|
37
|
+
assert.match(body, /Cleaner 02/);
|
|
38
|
+
assert.match(body, /Side brush jammed/);
|
|
39
|
+
});
|