@7365admin1/core 3.59.0 → 3.59.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 +10 -0
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/self-signup-files.test.mjs +154 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7365admin1/core",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "3.59.
|
|
4
|
+
"version": "3.59.1",
|
|
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/anpr-revoke.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/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/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/resident-default-role.util.ts src/utils/invite-role-scope.util.ts src/models/personal-emergency-chain.model.ts --format esm --out-dir test/.build --no-dts --silent && node --test test/*.test.mjs",
|
|
16
|
+
"test": "tsup src/config.ts src/utils/anpr-revoke.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/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/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/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 --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
|
},
|
|
@@ -0,0 +1,154 @@
|
|
|
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 { dropUnsentFiles } from "./.build/utils/self-signup-files.util.mjs";
|
|
7
|
+
import { schemaResidentSelfSignUp, schemaPerson } from "./.build/models/person.model.mjs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resident sign-up outage, 2026-09-08.
|
|
11
|
+
*
|
|
12
|
+
* A document whose upload failed stays in the app's list with no `id`
|
|
13
|
+
* (`UserDocumentStep.tsx:83-85` swallows the error), the wizard sends it, and
|
|
14
|
+
* `schemaFiles.id.required()` 400s the WHOLE registration with
|
|
15
|
+
* `"files[0].id" is required`. Measured against production: a body with no
|
|
16
|
+
* `files` reached the write, the same body with one id-less entry died at Joi.
|
|
17
|
+
* The resident sees nothing, because `createUser.tsx:458` only logs it.
|
|
18
|
+
*
|
|
19
|
+
* These pin the fix and, just as importantly, pin that it is STRICTLY WIDENING:
|
|
20
|
+
* everything that validated before still validates, identically.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// A body that is otherwise a perfectly good registration.
|
|
24
|
+
const base = () => ({
|
|
25
|
+
name: "Resident Test",
|
|
26
|
+
email: "resident@example.com",
|
|
27
|
+
password: "not-a-real-password",
|
|
28
|
+
type: "resident",
|
|
29
|
+
isOwner: true,
|
|
30
|
+
site: "0123456789abcdef01234567",
|
|
31
|
+
unit: "0123456789abcdef01234568",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const validFile = { id: "0123456789abcdef01234569", name: "ic-front.jpg", mimeType: "image/jpeg" };
|
|
35
|
+
// What the app sends after a failed upload: named, listed, and never uploaded.
|
|
36
|
+
const unsentFile = { name: "SEVEN365_Company_Profile_2026.pdf", mimeType: "application/pdf" };
|
|
37
|
+
|
|
38
|
+
const selfSignUp = (body) => schemaResidentSelfSignUp.validate(body, { abortEarly: false, stripUnknown: true });
|
|
39
|
+
|
|
40
|
+
test("the outage: a registration whose ONLY defect is an id-less file now goes through", () => {
|
|
41
|
+
const broken = { ...base(), files: [unsentFile] };
|
|
42
|
+
|
|
43
|
+
// Exactly the failure the owner's residents hit, before the fix.
|
|
44
|
+
const before = selfSignUp(broken);
|
|
45
|
+
assert.match(before.error?.message ?? "", /"files\[0\]\.id" is required/);
|
|
46
|
+
|
|
47
|
+
const after = selfSignUp(dropUnsentFiles(broken));
|
|
48
|
+
assert.equal(after.error, undefined);
|
|
49
|
+
// The applicant is kept, the attachment we never received is not.
|
|
50
|
+
assert.deepEqual(after.value.files, []);
|
|
51
|
+
assert.equal(after.value.email, "resident@example.com");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("a valid file id is still linked, byte for byte, in the same order", () => {
|
|
55
|
+
const second = { id: "0123456789abcdef0123456a", name: "ic-back.jpg", mimeType: "image/jpeg" };
|
|
56
|
+
const body = { ...base(), files: [validFile, second] };
|
|
57
|
+
|
|
58
|
+
const { error, value } = selfSignUp(dropUnsentFiles(body));
|
|
59
|
+
|
|
60
|
+
assert.equal(error, undefined);
|
|
61
|
+
assert.deepEqual(value.files, [validFile, second]);
|
|
62
|
+
// Nothing was reordered or rewritten - this is what the status-flip loop reads.
|
|
63
|
+
assert.equal(value.files[0].id, validFile.id);
|
|
64
|
+
assert.equal(value.files[1].id, second.id);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("a mixed list keeps every real upload and drops only the failed one", () => {
|
|
68
|
+
const body = { ...base(), files: [validFile, unsentFile] };
|
|
69
|
+
|
|
70
|
+
const { error, value } = selfSignUp(dropUnsentFiles(body));
|
|
71
|
+
|
|
72
|
+
assert.equal(error, undefined);
|
|
73
|
+
assert.deepEqual(value.files, [validFile]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("a body with no files key is returned completely unchanged", () => {
|
|
77
|
+
const body = base();
|
|
78
|
+
const out = dropUnsentFiles(body);
|
|
79
|
+
|
|
80
|
+
assert.deepEqual(out, body);
|
|
81
|
+
// Not `files: undefined` - the key must not appear at all, or a later
|
|
82
|
+
// `"files" in body` check silently changes meaning.
|
|
83
|
+
assert.equal("files" in out, false);
|
|
84
|
+
assert.equal(selfSignUp(out).error, undefined);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("a files value that is not an array is handed to the schema untouched", () => {
|
|
88
|
+
// `null` is explicitly allowed by the schema and must stay allowed; a string
|
|
89
|
+
// is not, and must still be refused by Joi rather than quietly swallowed here.
|
|
90
|
+
assert.equal(dropUnsentFiles({ ...base(), files: null }).files, null);
|
|
91
|
+
assert.equal(selfSignUp(dropUnsentFiles({ ...base(), files: null })).error, undefined);
|
|
92
|
+
|
|
93
|
+
assert.equal(dropUnsentFiles({ ...base(), files: "nonsense" }).files, "nonsense");
|
|
94
|
+
assert.match(selfSignUp(dropUnsentFiles({ ...base(), files: "nonsense" })).error?.message ?? "", /files/);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("an empty files array stays an empty files array", () => {
|
|
98
|
+
const { error, value } = selfSignUp(dropUnsentFiles({ ...base(), files: [] }));
|
|
99
|
+
assert.equal(error, undefined);
|
|
100
|
+
assert.deepEqual(value.files, []);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("the input body is not mutated", () => {
|
|
104
|
+
const body = { ...base(), files: [unsentFile] };
|
|
105
|
+
dropUnsentFiles(body);
|
|
106
|
+
assert.deepEqual(body.files, [unsentFile]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("the authenticated staff path is untouched - schemaPerson still refuses an id-less file", () => {
|
|
110
|
+
const staffBody = {
|
|
111
|
+
name: "Added By Staff",
|
|
112
|
+
email: "staff-added@example.com",
|
|
113
|
+
type: "resident",
|
|
114
|
+
site: "0123456789abcdef01234567",
|
|
115
|
+
files: [unsentFile],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// schemaPerson is what `add()` uses when there IS a caller. A staff client
|
|
119
|
+
// sending a file with no id is a bug in that client, and stays a 400.
|
|
120
|
+
assert.match(schemaPerson.validate(staffBody, { abortEarly: false }).error?.message ?? "", /"files\[0\]\.id" is required/);
|
|
121
|
+
// And it is refused for the same reason with the util applied, proving the
|
|
122
|
+
// util is not reachable from that path by accident.
|
|
123
|
+
assert.equal(schemaPerson.validate(staffBody, { abortEarly: false }).error !== undefined, true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The decision above is pure. What it cannot show is the WIRING: constructing
|
|
128
|
+
* the controller opens a live Mongo handle, so the only way to prove the filter
|
|
129
|
+
* runs on the self-signup path - and ONLY there - is to assert the source.
|
|
130
|
+
*/
|
|
131
|
+
const controller = readFileSync(
|
|
132
|
+
fileURLToPath(new URL("../src/controllers/person.controller.ts", import.meta.url)),
|
|
133
|
+
"utf8",
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
test("the filter runs on the self-signup path, before validation, and nowhere else", () => {
|
|
137
|
+
const selfSignUpFn = controller.slice(
|
|
138
|
+
controller.indexOf("async function addResidentSelfSignUp"),
|
|
139
|
+
controller.indexOf("async function add("),
|
|
140
|
+
);
|
|
141
|
+
assert.ok(selfSignUpFn.length > 0, "addResidentSelfSignUp was renamed or moved");
|
|
142
|
+
|
|
143
|
+
const dropAt = selfSignUpFn.indexOf("dropUnsentFiles(req.body)");
|
|
144
|
+
const validateAt = selfSignUpFn.indexOf("schemaResidentSelfSignUp.validate(");
|
|
145
|
+
assert.ok(dropAt > -1, "self-signup no longer drops unsent files");
|
|
146
|
+
assert.ok(validateAt > dropAt, "unsent files must be dropped BEFORE validation, or the 400 is unchanged");
|
|
147
|
+
assert.match(selfSignUpFn, /schemaResidentSelfSignUp\.validate\(\s*body\s*,/);
|
|
148
|
+
|
|
149
|
+
// Exactly one call site in the whole controller. If it ever appears on the
|
|
150
|
+
// authenticated `add`/`updateById` paths, staff stop being told about a
|
|
151
|
+
// client that is silently losing their attachments.
|
|
152
|
+
assert.equal(controller.match(/dropUnsentFiles\(/g)?.length, 1);
|
|
153
|
+
assert.match(controller, /import \{ dropUnsentFiles \} from "\.\.\/utils\/self-signup-files\.util"/);
|
|
154
|
+
});
|