@7365admin1/core 3.138.0 → 3.139.1
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 +12 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +172 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +172 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/service-provider-invite-email-new-company.e2e.test.mjs +123 -1
- package/test/member-working-membership.test.mjs +28 -0
package/package.json
CHANGED
|
@@ -354,12 +354,15 @@ describe("SP invitation: email status, Super Admin alert, new company typed by s
|
|
|
354
354
|
|
|
355
355
|
const repaired = await h.api(`/service-provider-invitations/${inviteId}/wrong-join/repair`, { method: "PUT", sid: adminSid });
|
|
356
356
|
assert.equal(repaired.status, 200, JSON.stringify(repaired.body));
|
|
357
|
-
|
|
357
|
+
// 2 memberships + the unused "Admin Service Provider" role left in JLL's role list
|
|
358
|
+
assert.equal(repaired.body.changed, 3);
|
|
358
359
|
assert.equal(await h.db.collection("members").countDocuments({ user: user._id, status: "active" }), 0);
|
|
360
|
+
assert.equal((await h.db.collection("roles").findOne({ _id: spRole })).status, "deleted");
|
|
359
361
|
|
|
360
362
|
const undone = await h.api(`/service-provider-invitations/${inviteId}/wrong-join/undo`, { method: "PUT", sid: adminSid });
|
|
361
363
|
assert.equal(undone.status, 200, JSON.stringify(undone.body));
|
|
362
364
|
assert.equal(await h.db.collection("members").countDocuments({ user: user._id, status: "active" }), 2);
|
|
365
|
+
assert.equal((await h.db.collection("roles").findOne({ _id: spRole })).status, "active", "undo restores the role");
|
|
363
366
|
|
|
364
367
|
await h.api(`/service-provider-invitations/${inviteId}/wrong-join/repair`, { method: "PUT", sid: adminSid });
|
|
365
368
|
const again = await h.api(`/service-provider-invitations/${inviteId}/wrong-join`, { sid: adminSid });
|
|
@@ -523,6 +526,125 @@ describe("SP invitation: email status, Super Admin alert, new company typed by s
|
|
|
523
526
|
assert.ok(await h.db.collection("customer.sites").findOne({ org: org._id, site, status: "active" }));
|
|
524
527
|
assert.equal((await h.db.collection("verifications").findOne({ _id: new ObjectId(inviteId) })).status, "complete");
|
|
525
528
|
});
|
|
529
|
+
|
|
530
|
+
// ------------------- E. "two organisations" after Finish setup (2026-09-30)
|
|
531
|
+
|
|
532
|
+
// latitude.sec@ on production after Repair + Finish setup: two INACTIVE
|
|
533
|
+
// JLL memberships (older) + the unused "*" role the old sign-up left in JLL,
|
|
534
|
+
// and its own Henderson Security company with org-level owner rows (no site)
|
|
535
|
+
// engaged at Latitude through customer.sites.
|
|
536
|
+
async function latitudeShape(email) {
|
|
537
|
+
const site = new ObjectId();
|
|
538
|
+
await h.db.collection("sites").insertOne({ _id: site, name: "Latitude", orgId: id.pmOrg, status: "active" });
|
|
539
|
+
const inviteId = await invite(email, "security_agency", site);
|
|
540
|
+
await approve(inviteId);
|
|
541
|
+
const sid = await newAccount(email);
|
|
542
|
+
const user = await h.db.collection("users").findOne({ email });
|
|
543
|
+
const spRole = new ObjectId();
|
|
544
|
+
await h.db.collection("roles").insertOne({ _id: spRole, name: "Admin Service Provider", type: "security_agency", org: id.pmOrg.toString(), site: site.toString(), permissions: ["*"], status: "active", createdAt: new Date(Date.now() + 500).toISOString() });
|
|
545
|
+
const later = new Date(Date.now() + 1000);
|
|
546
|
+
await h.db.collection("members").insertMany([
|
|
547
|
+
{ user: user._id, org: id.pmOrg, role: spRole, type: "organization", siteId: site, siteName: "Latitude", status: "active", createdAt: later },
|
|
548
|
+
{ user: user._id, org: id.pmOrg, role: spRole, type: "security_agency", siteId: site, siteName: "Latitude", status: "active", createdAt: later },
|
|
549
|
+
]);
|
|
550
|
+
await h.db.collection("users").updateOne({ _id: user._id }, { $set: { defaultOrg: id.pmOrg.toString() } });
|
|
551
|
+
// the 30 Sep Repair as it ran then: memberships only (the role stayed)
|
|
552
|
+
await h.db.collection("members").updateMany({ user: user._id, org: id.pmOrg }, { $set: { status: "inactive" } });
|
|
553
|
+
await h.db.collection("users").updateOne({ _id: user._id }, { $unset: { defaultOrg: "" } });
|
|
554
|
+
const applied = await h.api(`/service-provider-invitations/${inviteId}/finish-setup`, { method: "PUT", sid: adminSid, body: { companyName: "Henderson Security Services Pte Ltd" } });
|
|
555
|
+
assert.equal(applied.status, 200, JSON.stringify(applied.body));
|
|
556
|
+
return { site, inviteId, sid, user, spRole, orgId: applied.body.orgId };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
it("E1. every app finds the account's OWN company: membership lookup, organisation switcher, sites", async () => {
|
|
560
|
+
const { site, sid, user, orgId } = await latitudeShape("latitude-shape@e2e.example.com");
|
|
561
|
+
for (const app of ["organization", "security_agency"]) {
|
|
562
|
+
const m = await h.api(`/members/user/${user._id}/app/${app}`, { sid });
|
|
563
|
+
assert.equal(m.status, 200, JSON.stringify(m.body));
|
|
564
|
+
assert.equal(String(m.body.org), orgId, `${app}: Henderson, not the inactive JLL row`);
|
|
565
|
+
assert.equal(m.body.status, "active");
|
|
566
|
+
assert.ok(!m.body.siteId, "org-level owner row: no site of its own");
|
|
567
|
+
}
|
|
568
|
+
const orgs = await h.api(`/organizations/user/${user._id}?type=organization&limit=50`, { sid });
|
|
569
|
+
assert.equal(orgs.status, 200, JSON.stringify(orgs.body));
|
|
570
|
+
assert.deepEqual(orgs.body.items.map((o) => String(o.value)), [orgId], "one organisation, not JLL beside it");
|
|
571
|
+
// the security app's (and the mobile apps') site list: the engaged site
|
|
572
|
+
const sites = await h.api(`/customer-sites?org=${orgId}&status=active&limit=50`, { sid });
|
|
573
|
+
assert.equal(sites.status, 200, JSON.stringify(sites.body));
|
|
574
|
+
assert.deepEqual(sites.body.items.map((x) => String(x.site)), [site.toString()]);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("E1b. SECURITY: an inactive (repaired) membership reaches NOTHING in the sender - no members, roles, sites", async () => {
|
|
578
|
+
const { sid, orgId } = await latitudeShape("latitude-reach@e2e.example.com");
|
|
579
|
+
const jll = id.pmOrg.toString();
|
|
580
|
+
for (const path of [
|
|
581
|
+
`/members?org=${jll}&type=property_management_agency&status=active&limit=5`,
|
|
582
|
+
`/members?org=${jll}&type=organization&status=active&limit=5`,
|
|
583
|
+
`/roles?org=${jll}&limit=5`,
|
|
584
|
+
`/sites?org=${jll}&limit=5`,
|
|
585
|
+
`/customer-sites?org=${jll}&status=active&limit=5`,
|
|
586
|
+
]) {
|
|
587
|
+
const res = await h.api(path, { sid });
|
|
588
|
+
assert.ok([401, 403].includes(res.status), `${path} -> ${res.status} ${JSON.stringify(res.body).slice(0, 200)}`);
|
|
589
|
+
}
|
|
590
|
+
// its own company still works
|
|
591
|
+
const own = await h.api(`/members?org=${orgId}&type=organization&status=active&limit=5`, { sid });
|
|
592
|
+
assert.equal(own.status, 200, JSON.stringify(own.body));
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("E2. Repair on an already-repaired account retires the leftover full-access role only while nobody uses it; Undo restores it", async () => {
|
|
596
|
+
const { inviteId, spRole, user } = await latitudeShape("latitude-role@e2e.example.com");
|
|
597
|
+
const preview = await h.api(`/service-provider-invitations/${inviteId}/wrong-join`, { sid: adminSid });
|
|
598
|
+
assert.equal(preview.status, 200, JSON.stringify(preview.body));
|
|
599
|
+
assert.equal(preview.body.memberships.length, 0);
|
|
600
|
+
assert.deepEqual(preview.body.leftoverRoles.map((r) => r.name), ["Admin Service Provider"]);
|
|
601
|
+
|
|
602
|
+
// someone at JLL was since given that role -> it is JLL's now, never touched
|
|
603
|
+
const other = await h.db.collection("members").insertOne({ user: new ObjectId(), org: id.pmOrg, role: spRole, type: "security_agency", status: "active", createdAt: new Date() });
|
|
604
|
+
const kept = await h.api(`/service-provider-invitations/${inviteId}/wrong-join/repair`, { method: "PUT", sid: adminSid });
|
|
605
|
+
assert.equal(kept.body.changed, 0, JSON.stringify(kept.body));
|
|
606
|
+
assert.equal((await h.db.collection("roles").findOne({ _id: spRole })).status, "active");
|
|
607
|
+
await h.db.collection("members").deleteOne({ _id: other.insertedId });
|
|
608
|
+
|
|
609
|
+
const repaired = await h.api(`/service-provider-invitations/${inviteId}/wrong-join/repair`, { method: "PUT", sid: adminSid });
|
|
610
|
+
assert.equal(repaired.status, 200, JSON.stringify(repaired.body));
|
|
611
|
+
assert.equal(repaired.body.changed, 1);
|
|
612
|
+
assert.equal((await h.db.collection("roles").findOne({ _id: spRole })).status, "deleted");
|
|
613
|
+
// Henderson is untouched
|
|
614
|
+
assert.equal(await h.db.collection("members").countDocuments({ user: user._id, status: "active" }), 2);
|
|
615
|
+
|
|
616
|
+
const undone = await h.api(`/service-provider-invitations/${inviteId}/wrong-join/undo`, { method: "PUT", sid: adminSid });
|
|
617
|
+
assert.equal(undone.status, 200, JSON.stringify(undone.body));
|
|
618
|
+
assert.equal((await h.db.collection("roles").findOne({ _id: spRole })).status, "active");
|
|
619
|
+
// Undo of a role-only repair re-activates no membership
|
|
620
|
+
assert.equal(await h.db.collection("members").countDocuments({ user: user._id, org: id.pmOrg, status: "active" }), 0);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("E3. accept-new-company is idempotent per invitation: a retry after a half-finished attempt reuses the company; a second click is refused; one company", async () => {
|
|
624
|
+
const site = new ObjectId();
|
|
625
|
+
await h.db.collection("sites").insertOne({ _id: site, name: "Latitude", orgId: id.pmOrg, status: "active" });
|
|
626
|
+
const email = "double-click@e2e.example.com";
|
|
627
|
+
const inviteId = await invite(email, "security_agency", site);
|
|
628
|
+
await approve(inviteId);
|
|
629
|
+
const sid = await newAccount(email);
|
|
630
|
+
const user = await h.db.collection("users").findOne({ email });
|
|
631
|
+
// the first attempt died after creating the organisation (no roles, no members, invite still pending)
|
|
632
|
+
const half = new ObjectId();
|
|
633
|
+
await h.db.collection("organizations").insertOne({ _id: half, name: "Henderson Security Services Pte Ltd", type: "security_agency", nature: "security_agency", email, createdBy: user._id, invitedFrom: inviteId, status: "active", createdAt: new Date().toISOString() });
|
|
634
|
+
|
|
635
|
+
const retry = await h.api(`/service-provider-invitations/${inviteId}/accept-new-company`, { method: "PUT", sid, body: { companyName: "Henderson Security Services Pte Ltd" } });
|
|
636
|
+
assert.equal(retry.status, 200, JSON.stringify(retry.body));
|
|
637
|
+
assert.equal(retry.body.orgId, half.toString(), "the half-made company, not a second one");
|
|
638
|
+
assert.equal(await h.db.collection("organizations").countDocuments({ invitedFrom: inviteId }), 1);
|
|
639
|
+
const owners = await h.db.collection("members").find({ user: user._id, org: half, status: "active" }).toArray();
|
|
640
|
+
assert.deepEqual(owners.map((m) => m.type).sort(), ["organization", "security_agency"]);
|
|
641
|
+
assert.ok(await h.db.collection("customer.sites").findOne({ org: half, site, status: "active" }));
|
|
642
|
+
|
|
643
|
+
const again = await h.api(`/service-provider-invitations/${inviteId}/accept-new-company`, { method: "PUT", sid, body: { companyName: "Henderson Security Services Pte Ltd" } });
|
|
644
|
+
assert.equal(again.status, 400, JSON.stringify(again.body));
|
|
645
|
+
assert.equal(await h.db.collection("organizations").countDocuments({ createdBy: { $in: [user._id, user._id.toString()] } }), 1);
|
|
646
|
+
assert.equal(await h.db.collection("members").countDocuments({ user: user._id, org: half }), 2, "no duplicate owner rows");
|
|
647
|
+
});
|
|
526
648
|
});
|
|
527
649
|
|
|
528
650
|
async function seed(h) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// latitude.sec@ (2026-09-30): an INACTIVE membership in another organisation
|
|
2
|
+
// (the sender, after Repair) must never be the app's membership over the
|
|
3
|
+
// person's live one. See preferWorkingMembership in member-lookup.util.ts.
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { preferWorkingMembership, pickLookupMembership } from "./.build/utils/member-lookup.util.mjs";
|
|
7
|
+
|
|
8
|
+
const jll = { org: "jll", type: "organization", status: "inactive", siteId: "latitude" };
|
|
9
|
+
const hen = { org: "hen", type: "organization", status: "active", siteId: "" };
|
|
10
|
+
const other = { org: "oth", type: "organization", status: "active" };
|
|
11
|
+
|
|
12
|
+
test("a working row beats an older inactive one", () => {
|
|
13
|
+
assert.equal(preferWorkingMembership([jll, hen]), hen);
|
|
14
|
+
});
|
|
15
|
+
test("the default organisation wins among working rows", () => {
|
|
16
|
+
assert.equal(preferWorkingMembership([jll, other, hen], "HEN"), hen);
|
|
17
|
+
assert.equal(preferWorkingMembership([jll, other, hen]), other);
|
|
18
|
+
});
|
|
19
|
+
test("no working row: the old answer (never not-found), deleted last", () => {
|
|
20
|
+
const sus = { org: "a", status: "suspended" };
|
|
21
|
+
const del = { org: "b", status: "deleted" };
|
|
22
|
+
assert.equal(preferWorkingMembership([del, sus]), sus);
|
|
23
|
+
assert.equal(preferWorkingMembership([del]), del);
|
|
24
|
+
assert.equal(preferWorkingMembership([]), null);
|
|
25
|
+
});
|
|
26
|
+
test("the lookup used by the module gate follows the same rule", () => {
|
|
27
|
+
assert.equal(pickLookupMembership([jll, hen], "organization"), hen);
|
|
28
|
+
});
|