@7365admin1/core 3.86.0 → 3.88.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 +24 -0
- package/dist/index.d.ts +360 -5
- package/dist/index.js +6598 -5526
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6908 -5847
- 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 +17 -1
- package/test/e2e/robot-map-lift.e2e.test.mjs +297 -0
- 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/test/robot-map-lift.test.mjs +141 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
LIFT_DOOR,
|
|
6
|
+
LIFT_MODE,
|
|
7
|
+
LIFT_SIM_BOARD_MS,
|
|
8
|
+
LIFT_SIM_DOOR_DWELL_MS,
|
|
9
|
+
LIFT_SIM_MS_PER_FLOOR,
|
|
10
|
+
liftQueueKey,
|
|
11
|
+
simulatedLiftState,
|
|
12
|
+
} from "./.build/utils/lift-contract.util.mjs";
|
|
13
|
+
import {
|
|
14
|
+
buildingMapCreateSchema,
|
|
15
|
+
buildingMapUpdateSchema,
|
|
16
|
+
liftRequestSchema,
|
|
17
|
+
} from "./.build/models/robot-building-map.model.mjs";
|
|
18
|
+
|
|
19
|
+
/* The lift broker's car is REPLAYED from the session, so every API instance
|
|
20
|
+
* must reach the same answer for the same inputs. These pin the phases. */
|
|
21
|
+
|
|
22
|
+
const FLOORS = ["1", "2", "3", "4"];
|
|
23
|
+
const session = (over = {}) => ({ id: "s1", fromFloor: "1", toFloor: "3", activeAt: 0, ...over });
|
|
24
|
+
const at = (ms, s = session()) => simulatedLiftState({ name: "Lift A", floors: FLOORS, session: s, now: ms });
|
|
25
|
+
|
|
26
|
+
test("no holder: the car is idle, in human mode, held by nobody", async () => {
|
|
27
|
+
const { state, phase } = await simulatedLiftState({ name: "Lift A", floors: FLOORS, session: null, now: 5000 });
|
|
28
|
+
assert.equal(phase, "idle");
|
|
29
|
+
assert.equal(state.sessionId, "");
|
|
30
|
+
assert.equal(state.currentMode, LIFT_MODE.HUMAN);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("a session whose floors the lift does not serve is treated as no session", async () => {
|
|
34
|
+
const { phase } = await at(1000, session({ toFloor: "9" }));
|
|
35
|
+
assert.equal(phase, "idle");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("pickup at the car's own floor boards at once, doors open, AGV mode", async () => {
|
|
39
|
+
const { state, phase } = await at(0);
|
|
40
|
+
assert.equal(phase, "boarding");
|
|
41
|
+
assert.equal(state.doorState, LIFT_DOOR.OPEN);
|
|
42
|
+
assert.equal(state.currentMode, LIFT_MODE.AGV);
|
|
43
|
+
assert.equal(state.sessionId, "s1");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("boarding holds the doors longer than the dwell, then the car leaves", async () => {
|
|
47
|
+
const mid = await at(LIFT_SIM_BOARD_MS - 500);
|
|
48
|
+
assert.equal(mid.phase, "boarding");
|
|
49
|
+
assert.equal(mid.state.doorState, LIFT_DOOR.OPEN);
|
|
50
|
+
|
|
51
|
+
const moving = await at(LIFT_SIM_BOARD_MS + 1000);
|
|
52
|
+
assert.equal(moving.phase, "to-destination");
|
|
53
|
+
assert.equal(moving.state.doorState, LIFT_DOOR.CLOSED);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("arrives at the destination and keeps the doors open while held", async () => {
|
|
57
|
+
const travel = 2 * LIFT_SIM_MS_PER_FLOOR;
|
|
58
|
+
const arrived = await at(LIFT_SIM_BOARD_MS + travel + 1000);
|
|
59
|
+
assert.equal(arrived.phase, "arrived");
|
|
60
|
+
assert.equal(arrived.state.currentFloor, "3");
|
|
61
|
+
assert.equal(arrived.state.doorState, LIFT_DOOR.OPEN);
|
|
62
|
+
|
|
63
|
+
// Long after the door dwell: the hold is still refreshed, so no door closes on the robot.
|
|
64
|
+
const later = await at(LIFT_SIM_BOARD_MS + travel + LIFT_SIM_DOOR_DWELL_MS * 5);
|
|
65
|
+
assert.equal(later.state.doorState, LIFT_DOOR.OPEN);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("a pickup above the car travels there first", async () => {
|
|
69
|
+
const s = session({ fromFloor: "3", toFloor: "1" });
|
|
70
|
+
const start = await at(500, s);
|
|
71
|
+
assert.equal(start.phase, "to-pickup");
|
|
72
|
+
assert.equal(start.state.doorState, LIFT_DOOR.CLOSED);
|
|
73
|
+
const there = await at(2 * LIFT_SIM_MS_PER_FLOOR + 500, s);
|
|
74
|
+
assert.equal(there.phase, "boarding");
|
|
75
|
+
assert.equal(there.state.currentFloor, "3");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("replay is deterministic - same inputs, same answer", async () => {
|
|
79
|
+
const a = await at(12_345);
|
|
80
|
+
const b = await at(12_345);
|
|
81
|
+
assert.deepEqual(a, b);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("queue key separates same-named lifts in different buildings", () => {
|
|
85
|
+
assert.notEqual(liftQueueKey("a", "Lift A"), liftQueueKey("b", "Lift A"));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/* ---------------------------------------------------------------- schemas */
|
|
89
|
+
|
|
90
|
+
const robotId = "a".repeat(24);
|
|
91
|
+
const good = {
|
|
92
|
+
robotId,
|
|
93
|
+
name: "Tower 1",
|
|
94
|
+
levels: [{ floorNo: 1, elevationM: 0 }, { floorNo: 2, elevationM: 4.5 }],
|
|
95
|
+
doors: [{ name: "Lobby door", floorNo: 1, start: { x: 0, y: 0 }, end: { x: 1, y: 0 } }],
|
|
96
|
+
lifts: [{ name: "Lift A", x: 3, y: 4, floors: [1, 2] }],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
test("a sensible map validates and gets defaults", () => {
|
|
100
|
+
const { error, value } = buildingMapCreateSchema.validate(good);
|
|
101
|
+
assert.equal(error, undefined);
|
|
102
|
+
assert.equal(value.doors[0].type, "hinged");
|
|
103
|
+
assert.equal(value.lifts[0].widthM, 2);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("two lifts may not share a name - it is the queue's key", () => {
|
|
107
|
+
const { error } = buildingMapCreateSchema.validate({
|
|
108
|
+
...good,
|
|
109
|
+
lifts: [good.lifts[0], { ...good.lifts[0], x: 9 }],
|
|
110
|
+
});
|
|
111
|
+
assert.ok(error);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a lift must span at least two floors, and a level may not repeat", () => {
|
|
115
|
+
assert.ok(buildingMapCreateSchema.validate({ ...good, lifts: [{ ...good.lifts[0], floors: [1] }] }).error);
|
|
116
|
+
assert.ok(buildingMapCreateSchema.validate({ ...good, levels: [{ floorNo: 1 }, { floorNo: 1 }] }).error);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("pixel-sized coordinates are refused - the map is in metres", () => {
|
|
120
|
+
const { error } = buildingMapCreateSchema.validate({
|
|
121
|
+
...good,
|
|
122
|
+
doors: [{ ...good.doors[0], end: { x: 5000, y: 0 } }],
|
|
123
|
+
});
|
|
124
|
+
assert.ok(error);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("the estate cannot be sent - siteId/orgId are not accepted fields", () => {
|
|
128
|
+
assert.ok(buildingMapCreateSchema.validate({ ...good, siteId: robotId }).error);
|
|
129
|
+
assert.ok(buildingMapCreateSchema.validate({ ...good, orgId: robotId }).error);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("a save must say which version it edited", () => {
|
|
133
|
+
const { robotId: _r, ...body } = good;
|
|
134
|
+
assert.ok(buildingMapUpdateSchema.validate(body).error);
|
|
135
|
+
assert.equal(buildingMapUpdateSchema.validate({ ...body, version: 3 }).error, undefined);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("a lift request needs two different floors", () => {
|
|
139
|
+
assert.ok(liftRequestSchema.validate({ robotId, fromFloor: 2, toFloor: 2 }).error);
|
|
140
|
+
assert.equal(liftRequestSchema.validate({ robotId, fromFloor: 1, toFloor: 2 }).error, undefined);
|
|
141
|
+
});
|