@7365admin1/core 3.139.0 → 3.140.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@7365admin1/core",
3
3
  "license": "MIT",
4
- "version": "3.139.0",
4
+ "version": "3.140.0",
5
5
  "author": "7365admin1",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -572,6 +572,31 @@ describe("SP invitation: email status, Super Admin alert, new company typed by s
572
572
  const sites = await h.api(`/customer-sites?org=${orgId}&status=active&limit=50`, { sid });
573
573
  assert.equal(sites.status, 200, JSON.stringify(sites.body));
574
574
  assert.deepEqual(sites.body.items.map((x) => String(x.site)), [site.toString()]);
575
+ // the Account app's "iService365 Services" menu (owner 2026-10-01): Henderson only
576
+ const services = await h.api(`/members/users/${user._id}`, { sid });
577
+ assert.equal(services.status, 200, JSON.stringify(services.body));
578
+ assert.deepEqual([...new Set(services.body.items.map((m) => String(m.org)))], [orgId], "no inactive JLL rows");
579
+ // the history is still there on request (onboarding resume)
580
+ const history = await h.api(`/members/users/${user._id}?include=removed`, { sid });
581
+ assert.ok(history.body.items.some((m) => m.status === "inactive"));
582
+ });
583
+
584
+ it("E1b. SECURITY: an inactive (repaired) membership reaches NOTHING in the sender - no members, roles, sites", async () => {
585
+ const { sid, orgId } = await latitudeShape("latitude-reach@e2e.example.com");
586
+ const jll = id.pmOrg.toString();
587
+ for (const path of [
588
+ `/members?org=${jll}&type=property_management_agency&status=active&limit=5`,
589
+ `/members?org=${jll}&type=organization&status=active&limit=5`,
590
+ `/roles?org=${jll}&limit=5`,
591
+ `/sites?org=${jll}&limit=5`,
592
+ `/customer-sites?org=${jll}&status=active&limit=5`,
593
+ ]) {
594
+ const res = await h.api(path, { sid });
595
+ assert.ok([401, 403].includes(res.status), `${path} -> ${res.status} ${JSON.stringify(res.body).slice(0, 200)}`);
596
+ }
597
+ // its own company still works
598
+ const own = await h.api(`/members?org=${orgId}&type=organization&status=active&limit=5`, { sid });
599
+ assert.equal(own.status, 200, JSON.stringify(own.body));
575
600
  });
576
601
 
577
602
  it("E2. Repair on an already-repaired account retires the leftover full-access role only while nobody uses it; Undo restores it", async () => {
@@ -0,0 +1,60 @@
1
+ /*
2
+ * ONE "ENTITLED" SOURCE FOR EVERY LIST OF A PERSON'S ORGANISATIONS/SERVICES
3
+ * (owner 2026-10-01, latitude.sec@): the Account app's "iService365 Services"
4
+ * menu listed JLL Singapore under Security from two INACTIVE memberships.
5
+ * A taken-away row (deleted / inactive / soft-deleted) is listed nowhere and
6
+ * picked nowhere; "suspended" keeps today's behaviour (owner decision pending).
7
+ */
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { readFileSync } from "node:fs";
11
+
12
+ import {
13
+ entitledMemberships,
14
+ isEntitledMembership,
15
+ pickSiteMembership,
16
+ TAKEN_AWAY_MEMBER_STATUSES,
17
+ } from "./.build/utils/member-lookup.util.mjs";
18
+
19
+ const HENDERSON = "a".repeat(24);
20
+ const JLL = "b".repeat(24);
21
+ const LATITUDE = "c".repeat(24);
22
+
23
+ const rows = [
24
+ { id: "jll-org", org: JLL, type: "organization", siteId: LATITUDE, status: "inactive" },
25
+ { id: "jll-sec", org: JLL, type: "security_agency", siteId: LATITUDE, status: "inactive" },
26
+ { id: "hen-org", org: HENDERSON, type: "organization", siteId: "", status: "active" },
27
+ { id: "hen-sec", org: HENDERSON, type: "security_agency", siteId: "", status: "active" },
28
+ { id: "gone", org: JLL, type: "security_agency", status: "deleted" },
29
+ { id: "soft", org: JLL, type: "security_agency", status: "active", deletedAt: "2026-09-30" },
30
+ ];
31
+
32
+ test("the list keeps only memberships that still grant access", () => {
33
+ assert.deepEqual(entitledMemberships(rows).map((m) => m.id), ["hen-org", "hen-sec"]);
34
+ assert.deepEqual(entitledMemberships(null), []);
35
+ });
36
+
37
+ test("inactive and deleted are taken away; suspended still counts until the owner decides", () => {
38
+ assert.deepEqual(TAKEN_AWAY_MEMBER_STATUSES, ["deleted", "inactive"]);
39
+ assert.equal(isEntitledMembership({ status: "inactive" }), false);
40
+ assert.equal(isEntitledMembership({ status: "suspended" }), true);
41
+ assert.equal(isEntitledMembership({ status: "active" }), true);
42
+ });
43
+
44
+ test("an inactive row pinned to the site never wins the site lookup", () => {
45
+ assert.equal(pickSiteMembership(rows, "security_agency", LATITUDE, JLL), null);
46
+ });
47
+
48
+ test("GET /members/users/:id filters by default; ?include=removed is the history", () => {
49
+ const service = readFileSync("src/services/member.service.ts", "utf8");
50
+ assert.match(service, /includeRemoved \? all : entitledMemberships\(all\)/);
51
+ const controller = readFileSync("src/controllers/member.controller.ts", "utf8");
52
+ assert.match(controller, /includeRemoved: req\.query\.include === "removed"/);
53
+ assert.match(controller, /inOrg && isLiveMembership\(inOrg\) \? inOrg : null/);
54
+ });
55
+
56
+ test("notification categories and role permissions ignore taken-away rows", () => {
57
+ for (const file of ["src/services/notification-preference.service.ts", "src/utils/permission.util.ts"]) {
58
+ assert.match(readFileSync(file, "utf8"), /status: \{ \$nin: TAKEN_AWAY_MEMBER_STATUSES \}/, file);
59
+ }
60
+ });
@@ -28,9 +28,9 @@ test("the membership in the requested org is looked up first", () => {
28
28
  assert.match(BODY, /_getByUserIdTypeOrg\(user, type, org\)\.catch\(\(\) => null\)/);
29
29
  });
30
30
 
31
- test("a removed membership in that org never wins", () => {
32
- assert.match(BODY, /inOrg\.status !== "deleted"/);
33
- assert.match(BODY, /!inOrg\.deletedAt/);
31
+ test("a removed (or taken-away) membership in that org never wins", () => {
32
+ // isLiveMembership: not deleted / inactive, no deletedAt (member-entitled-list test)
33
+ assert.match(BODY, /inOrg && isLiveMembership\(inOrg\)/);
34
34
  });
35
35
 
36
36
  test("a miss (or a removed row) falls back to the old lookup instead of failing", () => {
@@ -74,6 +74,7 @@ test("a removed membership never wins; a suspended one is still returned, as bef
74
74
  assert.equal(pick([row("gone", { status: "deleted" })]), null);
75
75
  assert.equal(pick([row("s", { siteId: SITE, status: "suspended" })]), "s");
76
76
  assert.equal(isLiveMembership({ status: "suspended", deletedAt: "" }), true);
77
+ assert.equal(isLiveMembership({ status: "inactive", deletedAt: "" }), false);
77
78
  });
78
79
 
79
80
  test("another app type is never picked", () => {