From f1fa8eacec6d32490eb94d0a1cdbca9d3d706d40 Mon Sep 17 00:00:00 2001 From: david-rocca Date: Thu, 30 Apr 2026 16:35:32 -0400 Subject: [PATCH 1/3] Should now respect empty arrays --- api-docs/openapi.json | 116 ++++++++++++++++++ package.json | 2 +- schemas/registry-org/RootOrg.json | 25 +++- src/model/baseorg.js | 1 + src/repositories/baseOrgRepository.js | 2 +- src/utils/utils.js | 3 +- .../conversation/editConversationTest.js | 2 + .../registry-org/registryOrgCRUDTest.js | 2 + .../registry-org/rootOrgTest.js | 5 +- 9 files changed, 148 insertions(+), 10 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 1044a3c4a..5fd0bee55 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -4788,6 +4788,122 @@ } } }, + "/conversation/{uuid}": { + "put": { + "tags": [ + "Conversation" + ], + "summary": "Updates a conversation by UUID (accessible to Secretariat only)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Updates the conversation with the specified UUID

", + "operationId": "updateConversationByUUID", + "parameters": [ + { + "name": "uuid", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The UUID of the conversation to update" + }, + { + "$ref": "#/components/parameters/apiEntityHeader" + }, + { + "$ref": "#/components/parameters/apiUserHeader" + }, + { + "$ref": "#/components/parameters/apiSecretHeader" + } + ], + "responses": { + "200": { + "description": "Returns the updated conversation", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/conversation/conversation.json" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/bad-request.json" + } + } + } + }, + "401": { + "description": "Not Authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string", + "description": "The updated content of the conversation message" + }, + "visibility": { + "type": "string", + "enum": [ + "private", + "public" + ], + "description": "The updated visibility of the conversation message" + } + } + } + } + } + } + } + }, "/review/byUUID/{uuid}": { "get": { "tags": [ diff --git a/package.json b/package.json index 4cb9b3f9e..1e7be477c 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "start:prd": "node src/swagger.js && NODE_ENV=production node src/scripts/updateOpenapiHost.js && NODE_ENV=production node src/index.js", "swagger-autogen": "node src/swagger.js", "test": "NODE_ENV=test mocha --recursive --exit || true", - "test:integration": "NODE_ENV=test node-dev src/scripts/populate.js y; NODE_ENV=test MONGO_CONN_STRING=mongodb://docdb:27017 MONGO_DB_NAME=cve_test node-dev src/scripts/migrate.js; NODE_ENV=test mocha test/integration-tests --recursive --exit", + "test:integration": "NODE_ENV=test node-dev src/scripts/populate.js y; NODE_ENV=test MONGO_CONN_STRING=mongodb://localhost:27017 MONGO_DB_NAME=cve_test node-dev src/scripts/migrate.js; NODE_ENV=test mocha test/integration-tests --recursive --exit", "test:unit-tests": "NODE_ENV=test mocha test/unit-tests --recursive --exit || true", "test:coverage": "NODE_ENV=test nyc --reporter=text mocha src/* --recursive --exit || true", "test:coverage-html": "NODE_ENV=test nyc --reporter=html mocha src/* --recursive --exit || true", diff --git a/schemas/registry-org/RootOrg.json b/schemas/registry-org/RootOrg.json index 9015ebdf5..d4c090444 100644 --- a/schemas/registry-org/RootOrg.json +++ b/schemas/registry-org/RootOrg.json @@ -16,6 +16,24 @@ "maxLength": 32 }, "aliases": { "$ref": "/BaseOrg#/properties/aliases" }, + "private_contacts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "phone": { + "type": "string" + }, + "poc": { + "type": "string" + }, + "poc_email": { + "type": "string" + } + }, + "additionalProperties": false + } + }, "contact_info": { "type": "object", "properties": { @@ -38,8 +56,7 @@ "type": "array", "uniqueItems": true, "items": { - "type": "string", - "format": "uuid" + "$ref": "/BaseOrg#/definitions/uuidType" } }, "partner_role_type": { @@ -54,9 +71,7 @@ "advisory_locations": { "$ref": "/BaseOrg#/properties/advisory_locations" }, "program_data": { "$ref": "/BaseOrg#/properties/program_data" }, "industry": { "$ref": "/BaseOrg#/properties/industry" }, - "top_level_root": { "$ref": "/BaseOrg#/properties/top_level_root" }, - "tl_root_start_date": { "$ref": "/BaseOrg#/properties/tl_root_start_date" }, - "is_cna_discussion_list": { "$ref": "/BaseOrg#/properties/is_cna_discussion_list" } + "top_level_root": { "$ref": "/BaseOrg#/properties/top_level_root" } }, "required": ["short_name"] } diff --git a/src/model/baseorg.js b/src/model/baseorg.js index 1f59c2cc7..13777a5af 100644 --- a/src/model/baseorg.js +++ b/src/model/baseorg.js @@ -21,6 +21,7 @@ const schema = { website: String }, private_contacts: [{ + _id: false, phone: String, poc: String, poc_email: String diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index da27ff3d6..ca6684f06 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -938,7 +938,7 @@ class BaseOrgRepository extends BaseRepository { if (hasJointApprovalChanges) { // write the joint approval to the database - jointApprovalRegistry = _.merge({}, registryOrg.toObject(), registryObjectRaw) + jointApprovalRegistry = _.mergeWith({}, registryOrg.toObject(), registryObjectRaw, skipNulls) if (reviewObject) { await reviewObjectRepo.updateReviewOrgObject(jointApprovalRegistry, reviewObject.uuid, options) } else { diff --git a/src/utils/utils.js b/src/utils/utils.js index 82e022ec7..d30c4252d 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -309,8 +309,7 @@ function deepRemoveEmpty (obj) { // This will catch both initially empty fields and nested objects that became empty. if ( value === null || - (_.isObject(value) && !_.isDate(value) && _.isEmpty(value)) || - (_.isArray(value) && _.isEmpty(value)) + (_.isObject(value) && !_.isArray(value) && !_.isDate(value) && _.isEmpty(value)) ) { delete currentObj[key] } diff --git a/test/integration-tests/conversation/editConversationTest.js b/test/integration-tests/conversation/editConversationTest.js index 24b1ae440..9697a2173 100644 --- a/test/integration-tests/conversation/editConversationTest.js +++ b/test/integration-tests/conversation/editConversationTest.js @@ -31,6 +31,8 @@ describe('Testing Conversation edit by index endpoint', () => { delete org.admins delete org.users delete org.top_level_root + delete org.oversees + delete org.program_data }) await chai diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js index ee5dd557d..2c49f3a63 100644 --- a/test/integration-tests/registry-org/registryOrgCRUDTest.js +++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js @@ -71,6 +71,8 @@ describe('Testing /registryOrg endpoints', () => { createdOrg = res.body.created delete createdOrg.created delete createdOrg.last_updated + delete createdOrg.users + delete createdOrg.admins }) }) }) diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 0ea61393a..cbb251087 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -33,6 +33,9 @@ describe('Testing ROOT Organization Type', () => { delete createdOrg.created delete createdOrg.last_updated delete createdOrg.program_data + delete createdOrg.users + delete createdOrg.admins + delete createdOrg.oversees }) }) @@ -85,7 +88,7 @@ describe('Testing ROOT Organization Type', () => { long_name: 'Updated Root Org Test' }) .then((res) => { - if (res.status === 400) console.log(JSON.stringify(res.body, null, 2)) + if (res.status !== 200) console.log('403 Response:', JSON.stringify(res.body, null, 2)) expect(res).to.have.status(200) }) }) From a11961becac6f4742da505185fd174d66c24053e Mon Sep 17 00:00:00 2001 From: david-rocca Date: Thu, 30 Apr 2026 16:45:30 -0400 Subject: [PATCH 2/3] Deal with string vs date issue --- src/repositories/baseOrgRepository.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index ca6684f06..32ab42b20 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -807,10 +807,15 @@ class BaseOrgRepository extends BaseRepository { jointApprovalFields = getConstants().JOINT_APPROVAL_FIELDS } + // Convert Mongoose docs to plain objects and serialize to match stringified formats + const originalPlain = orgObjectOriginal.toObject ? orgObjectOriginal.toObject() : orgObjectOriginal + const originalSerialized = JSON.parse(JSON.stringify(originalPlain)) + const updatedSerialized = JSON.parse(JSON.stringify(orgObjectUpdated)) + // Filter the list to find only fields that have changed const changedFields = _.filter(jointApprovalFields, field => { // Check if the value in the original object is different from the updated object - return _.get(orgObjectOriginal, field) !== _.get(orgObjectUpdated, field) + return !_.isEqual(_.get(originalSerialized, field), _.get(updatedSerialized, field)) }) // Return the array of fields that had changes (will be empty if none changed) @@ -932,8 +937,8 @@ class BaseOrgRepository extends BaseRepository { // Check if there are actual changes to joint approval fields compared to current org object (not current review) // Only compare fields that are actually in the incoming data const incomingJointApprovalKeys = Object.keys(_.pick(registryObjectRaw, jointApprovalFieldsRegistry)) - const currentJointApprovalData = _.pick(registryOrg.toObject(), incomingJointApprovalKeys) - const incomingJointApprovalData = _.pick(registryObjectRaw, incomingJointApprovalKeys) + const currentJointApprovalData = JSON.parse(JSON.stringify(_.pick(registryOrg.toObject(), incomingJointApprovalKeys))) + const incomingJointApprovalData = JSON.parse(JSON.stringify(_.pick(registryObjectRaw, incomingJointApprovalKeys))) const hasJointApprovalChanges = !_.isEqual(currentJointApprovalData, incomingJointApprovalData) if (hasJointApprovalChanges) { From 62d5b275555d6f359e9293baf4ab32cf133a96ce Mon Sep 17 00:00:00 2001 From: david-rocca Date: Thu, 30 Apr 2026 16:50:18 -0400 Subject: [PATCH 3/3] removed accidental commit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e7be477c..4cb9b3f9e 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "start:prd": "node src/swagger.js && NODE_ENV=production node src/scripts/updateOpenapiHost.js && NODE_ENV=production node src/index.js", "swagger-autogen": "node src/swagger.js", "test": "NODE_ENV=test mocha --recursive --exit || true", - "test:integration": "NODE_ENV=test node-dev src/scripts/populate.js y; NODE_ENV=test MONGO_CONN_STRING=mongodb://localhost:27017 MONGO_DB_NAME=cve_test node-dev src/scripts/migrate.js; NODE_ENV=test mocha test/integration-tests --recursive --exit", + "test:integration": "NODE_ENV=test node-dev src/scripts/populate.js y; NODE_ENV=test MONGO_CONN_STRING=mongodb://docdb:27017 MONGO_DB_NAME=cve_test node-dev src/scripts/migrate.js; NODE_ENV=test mocha test/integration-tests --recursive --exit", "test:unit-tests": "NODE_ENV=test mocha test/unit-tests --recursive --exit || true", "test:coverage": "NODE_ENV=test nyc --reporter=text mocha src/* --recursive --exit || true", "test:coverage-html": "NODE_ENV=test nyc --reporter=html mocha src/* --recursive --exit || true",