@7365admin1/core 3.52.9 → 3.52.10
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 +6 -0
- package/dist/index.d.ts +58 -3
- package/dist/index.js +952 -862
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1259 -1170
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/vehicle-site-scope.test.mjs +128 -0
package/package.json
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { entitledSiteScope } from "./.build/utils/camera-view.util.mjs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `GET /api/vehicles` and `GET /api/vehicles/blocklisted` take an OPTIONAL
|
|
10
|
+
* `site`, and a request that omitted it applied no site predicate at all:
|
|
11
|
+
* `if (site) await requireSiteReach(...)` in the controller and
|
|
12
|
+
* `...(site && { site })` in the repository. Measured read-only on staging,
|
|
13
|
+
* 2026-09-03: one such call from the MA app's vehicle screen came back with 148
|
|
14
|
+
* vehicles belonging to four unrelated clients.
|
|
15
|
+
*
|
|
16
|
+
* The DECISION is `entitledSiteScope` and is covered here directly. The WIRING
|
|
17
|
+
* — that the controller asks for the scope and the repository applies it — needs
|
|
18
|
+
* a live Atlas connection to run, so it is asserted on the source, exactly as
|
|
19
|
+
* `camera-entitlement-wiring.test.mjs` does for the camera paths.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const ORG_A = "aaaaaaaaaaaaaaaaaaaaaaaa";
|
|
23
|
+
const ORG_B = "bbbbbbbbbbbbbbbbbbbbbbbb";
|
|
24
|
+
const SITE_A1 = "a1a1a1a1a1a1a1a1a1a1a1a1";
|
|
25
|
+
const SITE_A2 = "a2a2a2a2a2a2a2a2a2a2a2a2";
|
|
26
|
+
const SITE_B1 = "b1b1b1b1b1b1b1b1b1b1b1b1";
|
|
27
|
+
|
|
28
|
+
const sitesByOrg = { [ORG_A]: [SITE_A1, SITE_A2], [ORG_B]: [SITE_B1] };
|
|
29
|
+
|
|
30
|
+
test("a site-level role reaches only its own site", () => {
|
|
31
|
+
const scope = entitledSiteScope({
|
|
32
|
+
memberships: [{ org: ORG_A, siteId: SITE_A1, orgLevelRole: false }],
|
|
33
|
+
sitesByOrg,
|
|
34
|
+
engagedSitesByOrg: {},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
assert.deepEqual(scope, [SITE_A1]);
|
|
38
|
+
assert.ok(!scope.includes(SITE_A2), "must not reach a sibling site");
|
|
39
|
+
assert.ok(!scope.includes(SITE_B1), "must never reach another organisation");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("an org-level role reaches its whole organisation and no other", () => {
|
|
43
|
+
// The row also NAMES a site — the case that was the defect in the first cut of
|
|
44
|
+
// the camera change (owner decision, 2026-09-02: org-level roles see all
|
|
45
|
+
// sites).
|
|
46
|
+
const scope = entitledSiteScope({
|
|
47
|
+
memberships: [{ org: ORG_A, siteId: SITE_A1, orgLevelRole: true }],
|
|
48
|
+
sitesByOrg,
|
|
49
|
+
engagedSitesByOrg: {},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
assert.deepEqual([...scope].sort(), [SITE_A1, SITE_A2].sort());
|
|
53
|
+
assert.ok(!scope.includes(SITE_B1), "must never reach another organisation");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a membership carrying no site is org-wide", () => {
|
|
57
|
+
const scope = entitledSiteScope({
|
|
58
|
+
memberships: [{ org: ORG_A, siteId: "", orgLevelRole: false }],
|
|
59
|
+
sitesByOrg,
|
|
60
|
+
engagedSitesByOrg: {},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
assert.deepEqual([...scope].sort(), [SITE_A1, SITE_A2].sort());
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("an engagement grants only through a membership with no site", () => {
|
|
67
|
+
const engagedSitesByOrg = { [ORG_A]: [SITE_B1] };
|
|
68
|
+
|
|
69
|
+
const orgWide = entitledSiteScope({
|
|
70
|
+
memberships: [{ org: ORG_A, siteId: "", orgLevelRole: false }],
|
|
71
|
+
sitesByOrg,
|
|
72
|
+
engagedSitesByOrg,
|
|
73
|
+
});
|
|
74
|
+
assert.ok(orgWide.includes(SITE_B1), "an agency's org-level staff reach it");
|
|
75
|
+
|
|
76
|
+
// A membership PINNED to a site never grants a different site — the same rule
|
|
77
|
+
// `cameraGrant` applies (owner decision, 2026-08-10).
|
|
78
|
+
const pinned = entitledSiteScope({
|
|
79
|
+
memberships: [{ org: ORG_A, siteId: SITE_A1, orgLevelRole: false }],
|
|
80
|
+
sitesByOrg,
|
|
81
|
+
engagedSitesByOrg,
|
|
82
|
+
});
|
|
83
|
+
assert.deepEqual(pinned, [SITE_A1]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("no membership is an empty scope, not an unrestricted one", () => {
|
|
87
|
+
assert.deepEqual(
|
|
88
|
+
entitledSiteScope({ memberships: [], sitesByOrg, engagedSitesByOrg: {} }),
|
|
89
|
+
[],
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const controller = readFileSync(
|
|
94
|
+
fileURLToPath(new URL("../src/controllers/vehicle.controller.ts", import.meta.url)),
|
|
95
|
+
"utf8",
|
|
96
|
+
);
|
|
97
|
+
const repo = readFileSync(
|
|
98
|
+
fileURLToPath(new URL("../src/repositories/vehicle.repo.ts", import.meta.url)),
|
|
99
|
+
"utf8",
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
test("both vehicle lists resolve a scope when no site is named", () => {
|
|
103
|
+
const asked = controller.match(/const siteScope = site \? null : await entitledSites\(req\);/g);
|
|
104
|
+
assert.equal(asked?.length, 2, "the list and the blocklist both ask");
|
|
105
|
+
|
|
106
|
+
// An explicitly supplied site is still authorised, and never trusted.
|
|
107
|
+
assert.equal(
|
|
108
|
+
controller.match(/if \(site\) await requireSiteReach\(req, site\);/g)?.length,
|
|
109
|
+
2,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
assert.ok(
|
|
113
|
+
controller.includes(" siteScope,\n"),
|
|
114
|
+
"the scope must reach the repository, not be resolved and dropped",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("the repository applies an empty scope rather than dropping it", () => {
|
|
119
|
+
const applied = repo.match(/\.\.\.\(siteScope\n/g);
|
|
120
|
+
assert.equal(applied?.length, 2, "both queries carry the predicate");
|
|
121
|
+
|
|
122
|
+
// `siteScope && siteScope.length` here would turn "reaches no site" back into
|
|
123
|
+
// "sees everything", which is the whole defect.
|
|
124
|
+
assert.ok(
|
|
125
|
+
!/siteScope\?\.length|siteScope && siteScope\.length/.test(repo),
|
|
126
|
+
"an empty array must not collapse into the no-restriction branch",
|
|
127
|
+
);
|
|
128
|
+
});
|