diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 5bcc9895f..6c00ce16a 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -5078,7 +5078,7 @@ } } }, - "/review/org/{uuid}": { + "/review/{uuid}": { "put": { "tags": [ "Review Object" @@ -5181,7 +5181,7 @@ } } }, - "/review/org/{uuid}/approve": { + "/review/{uuid}/approve": { "put": { "tags": [ "Review Object" @@ -5285,7 +5285,7 @@ } } }, - "/review/org/{uuid}/reject": { + "/review/{uuid}/reject": { "put": { "tags": [ "Review Object" diff --git a/package-lock.json b/package-lock.json index 60e6897f6..563835e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -822,9 +822,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -891,9 +891,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -3523,9 +3523,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -3591,9 +3591,9 @@ } }, "node_modules/eslint-plugin-node/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -3762,9 +3762,9 @@ "dev": true }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -4518,9 +4518,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6176,12 +6176,12 @@ } }, "node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.9.tgz", + "integrity": "sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=10" @@ -6272,9 +6272,9 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -6500,9 +6500,9 @@ } }, "node_modules/multimatch/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -6705,9 +6705,9 @@ } }, "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -7715,9 +7715,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dependencies": { "side-channel": "^1.1.0" }, @@ -9345,9 +9345,9 @@ } }, "node_modules/standard/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -9792,9 +9792,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -10068,9 +10068,9 @@ "dev": true }, "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==" }, "node_modules/undici-types": { "version": "6.20.0", @@ -11149,9 +11149,9 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -11199,9 +11199,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -13191,9 +13191,9 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -13322,9 +13322,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -13373,9 +13373,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -13957,9 +13957,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "requires": { "brace-expansion": "^1.1.7" } @@ -15166,12 +15166,12 @@ } }, "minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.9.tgz", + "integrity": "sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" } }, "minimist": { @@ -15236,9 +15236,9 @@ } }, "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -15390,9 +15390,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -15550,9 +15550,9 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -16299,9 +16299,9 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "requires": { "side-channel": "^1.1.0" } @@ -17467,9 +17467,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -17795,9 +17795,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -18006,9 +18006,9 @@ "dev": true }, "underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==" }, "undici-types": { "version": "6.20.0", diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js index 05b9fc572..d7f234603 100644 --- a/src/controller/org.controller/org.controller.js +++ b/src/controller/org.controller/org.controller.js @@ -17,10 +17,8 @@ const validateUUID = require('uuid').validate */ async function getOrgs (req, res, next) { try { - const session = await mongoose.startSession() const repo = req.ctx.repositories.getBaseOrgRepository() const CONSTANTS = getConstants() - let returnValue // temporary measure to allow tests to work after fixing #920 // tests required changing the global limit to force pagination @@ -32,11 +30,7 @@ async function getOrgs (req, res, next) { options.sort = { short_name: 'asc' } options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value - try { - returnValue = await repo.getAllOrgs({ ...options, session }, true) - } finally { - await session.endSession() - } + const returnValue = await repo.getAllOrgs({ ...options }, true) logger.info({ uuid: req.ctx.uuid, message: 'The orgs were sent to the user.' }) return res.status(200).json(returnValue) @@ -58,7 +52,6 @@ async function getOrgs (req, res, next) { */ async function getOrg (req, res, next) { try { - const session = await mongoose.startSession() const repo = req.ctx.repositories.getBaseOrgRepository() const requesterOrgShortName = req.ctx.org const identifier = req.ctx.params.identifier @@ -67,26 +60,21 @@ async function getOrg (req, res, next) { let returnValue try { - session.startTransaction() - const requesterOrg = await repo.findOneByShortName(requesterOrgShortName, { session }, returnLegacyFormat) + const requesterOrg = await repo.findOneByShortName(requesterOrgShortName, {}, returnLegacyFormat) const requesterOrgIdentifier = identifierIsUUID ? requesterOrg.UUID : requesterOrgShortName - const isSecretariat = await repo.isSecretariat(requesterOrg, { session }, returnLegacyFormat) + const isSecretariat = await repo.isSecretariat(requesterOrg, {}, returnLegacyFormat) if (requesterOrgIdentifier !== identifier && !isSecretariat) { logger.info({ uuid: req.ctx.uuid, message: identifier + ' organization can only be viewed by the users of the same organization or the Secretariat.' }) return res.status(403).json(error.notSameOrgOrSecretariat()) } - returnValue = await repo.getOrg(identifier, identifierIsUUID, { session }, returnLegacyFormat) + returnValue = await repo.getOrg(identifier, identifierIsUUID, {}, returnLegacyFormat) } catch (error) { - await session.abortTransaction() // Handle the specific error thrown by BaseOrgRepository.createOrg if (error.message && error.message.includes('Unknown Org type requested')) { return res.status(400).json({ message: error.message }) } - throw error - } finally { - await session.endSession() } if (!returnValue) { // an empty result can only happen if the requestor is the Secretariat logger.info({ uuid: req.ctx.uuid, message: identifier + ' organization does not exist.' }) @@ -187,13 +175,13 @@ async function getUser (req, res, next) { return res.status(404).json(error.userDne(username)) } - const rawResult = result + const rawResult = result.toObject() delete rawResult._id delete rawResult.__v delete rawResult.secret - logger.info({ uuid: req.ctx.uuid, message: username + ' was sent to the user.', user: result }) + logger.info({ uuid: req.ctx.uuid, message: username + ' was sent to the user.', user: rawResult }) return res.status(200).json(rawResult) } catch (err) { next(err) @@ -202,7 +190,7 @@ async function getUser (req, res, next) { /** * Get details on ID quota for an org with the specified org shortname. - * Called by GET /api/registry/org/{shortname}/id_quota, GET /api/org/{shortname}/id_quota + * Called by GET /api/registry/org/{shortname}/hard_quota, GET /api/org/{shortname}/id_quota * * @param {Object} req - The request object * @param {Object} res - The response object @@ -336,7 +324,7 @@ async function updateOrg (req, res, next) { const shortNameUrlParameter = req.ctx.params.shortname const orgRepository = req.ctx.repositories.getBaseOrgRepository() - const session = await mongoose.startSession() + const session = await mongoose.startSession({ causalConsistency: false }) let responseMessage // Get the query parameters as JSON // These are validated by the middleware in org/index.js @@ -448,7 +436,7 @@ async function createUser (req, res, next) { return res.status(400).json({ message: 'Parameters were invalid', errors: result.errors }) } } else { - if (!body?.username || typeof body?.username !== 'string' || !body?.username.length > 0) { + if (!body?.username || typeof body?.username !== 'string') { return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'username', msg: 'Parameter must be a non empty string' }] }) } } diff --git a/src/controller/registry-org.controller/registry-org.controller.js b/src/controller/registry-org.controller/registry-org.controller.js index ba0ca21c5..7623505c0 100644 --- a/src/controller/registry-org.controller/registry-org.controller.js +++ b/src/controller/registry-org.controller/registry-org.controller.js @@ -20,10 +20,9 @@ const validateUUID = require('uuid').validate */ async function getAllOrgs (req, res, next) { try { - const session = await mongoose.startSession() const repo = req.ctx.repositories.getBaseOrgRepository() const conversationRepo = req.ctx.repositories.getConversationRepository() - const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org, { session }) + const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org) const CONSTANTS = getConstants() let returnValue @@ -38,14 +37,17 @@ async function getAllOrgs (req, res, next) { options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value try { - returnValue = await repo.getAllOrgs({ ...options, session }) + returnValue = await repo.getAllOrgs({ ...options }) // fetch conversations for (let i = 0; i < returnValue.organizations.length; i++) { - const conversation = await conversationRepo.getAllByTargetUUID(returnValue.organizations[i].UUID, isSecretariat, { session }) + const conversation = await conversationRepo.getAllByTargetUUID(returnValue.organizations[i].UUID, isSecretariat) returnValue.organizations[i].conversation = conversation?.length ? conversation : undefined } - } finally { - await session.endSession() + } catch (error) { + // Handle the specific error thrown by BaseOrgRepository.createOrg + if (error.message && error.message.includes('Unknown Org type requested')) { + return res.status(400).json({ message: error.message }) + } } logger.info({ uuid: req.ctx.uuid, message: 'The orgs were sent to the user.' }) @@ -69,7 +71,6 @@ async function getAllOrgs (req, res, next) { */ async function getOrg (req, res, next) { try { - const session = await mongoose.startSession() const repo = req.ctx.repositories.getBaseOrgRepository() const conversationRepo = req.ctx.repositories.getConversationRepository() // User passed in parameter to filter for @@ -79,32 +80,28 @@ async function getOrg (req, res, next) { let returnValue try { - session.startTransaction() - const requesterOrg = await repo.findOneByShortName(requesterOrgShortName, { session }) + const requesterOrg = await repo.findOneByShortName(requesterOrgShortName) const requesterOrgIdentifier = identifierIsUUID ? requesterOrg.UUID : requesterOrgShortName - const isSecretariat = await repo.isSecretariat(requesterOrg, { session }) + const isSecretariat = await repo.isSecretariat(requesterOrg) if (requesterOrgIdentifier !== identifier && !isSecretariat) { logger.info({ uuid: req.ctx.uuid, message: identifier + ' organization can only be viewed by the users of the same organization or the Secretariat.' }) return res.status(403).json(error.notSameOrgOrSecretariat()) } - returnValue = await repo.getOrg(identifier, identifierIsUUID, { session }) + returnValue = await repo.getOrg(identifier, identifierIsUUID) if (returnValue) { // fetch conversation - const conversation = await conversationRepo.getAllByTargetUUID(returnValue.UUID, isSecretariat, { session }) + const conversation = await conversationRepo.getAllByTargetUUID(returnValue.UUID, isSecretariat) returnValue.conversation = conversation?.length ? _.map(conversation, c => _.omit(c, ['__v', '_id', 'UUID', 'previous_conversation_uuid', 'next_conversation_uuid', 'target_uuid', 'visibility'])) : undefined } } catch (error) { - await session.abortTransaction() // Handle the specific error thrown by BaseOrgRepository.createOrg if (error.message && error.message.includes('Unknown Org type requested')) { return res.status(400).json({ message: error.message }) } throw error - } finally { - await session.endSession() } if (!returnValue) { // an empty result can only happen if the requestor is the Secretariat logger.info({ uuid: req.ctx.uuid, message: identifier + ' organization does not exist.' }) @@ -132,7 +129,7 @@ async function getOrg (req, res, next) { */ async function createOrg (req, res, next) { try { - const session = await mongoose.startSession() + const session = await mongoose.startSession({ causalConsistency: false }) const repo = req.ctx.repositories.getBaseOrgRepository() const body = req.ctx.body const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org, { session }) @@ -230,7 +227,7 @@ async function createOrg (req, res, next) { */ async function updateOrg (req, res, next) { try { - const session = await mongoose.startSession() + const session = await mongoose.startSession({ causalConsistency: false }) const shortName = req.ctx.params.shortname const repo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getBaseUserRepository() @@ -316,15 +313,18 @@ async function updateOrg (req, res, next) { } } + // Update Org full will cause a write to the Conversations collection, to avoid a read-after-write issue, we need to get the previous conversation data first + const previousConversation = await conversationRepo.getAllByTargetUUID(await repo.getOrgUUID(shortName, { session }), isSecretariat, { session }) || [] + updatedOrg = await repo.updateOrgFull(shortName, req.ctx.body, { session }, false, requestingUser.UUID, isAdmin, isSecretariat) jointApprovalRequired = _.get(updatedOrg, 'joint_approval_required', false) _.unset(updatedOrg, 'joint_approval_required') - - await session.commitTransaction() - session.startTransaction() - // Checking for existing Conversations - const existingConversations = await conversationRepo.getAllByTargetUUID(updatedOrg.UUID, isSecretariat, { session }) || [] - updatedOrg.conversation = existingConversations.map(c => _.omit(c, ['__v', '_id', 'previous_conversation_uuid', 'next_conversation_uuid'])) + // append previous conversations to any conversations that are in the org already + const currentConversations = Array.isArray(updatedOrg?.conversation) ? updatedOrg.conversation : [] + const prevConversations = Array.isArray(previousConversation) ? previousConversation : [] + if (updatedOrg) { + updatedOrg.conversation = [...currentConversations, ...prevConversations].map(c => _.omit(c, ['__v', '_id', 'previous_conversation_uuid', 'next_conversation_uuid'])) + } await session.commitTransaction() } catch (updateErr) { @@ -385,7 +385,7 @@ async function updateOrg (req, res, next) { */ async function deleteOrg (req, res, next) { try { - const session = await mongoose.startSession() + const session = await mongoose.startSession({ causalConsistency: false }) const repo = req.ctx.repositories.getBaseOrgRepository() const shortName = req.ctx.params.identifier @@ -498,7 +498,7 @@ async function getUsers (req, res, next) { * Called by POST /api/registryOrg/:shortname/user */ async function createUserByOrg (req, res, next) { - const session = await mongoose.startSession() + const session = await mongoose.startSession({ causalConsistency: false }) try { const body = req.ctx.body const userRepo = req.ctx.repositories.getBaseUserRepository() diff --git a/src/controller/registry-user.controller/registry-user.controller.js b/src/controller/registry-user.controller/registry-user.controller.js index fdd11eaf7..658a24ed2 100644 --- a/src/controller/registry-user.controller/registry-user.controller.js +++ b/src/controller/registry-user.controller/registry-user.controller.js @@ -21,9 +21,7 @@ const _ = require('lodash') async function getAllUsers (req, res, next) { try { const CONSTANTS = getConstants() - const session = await mongoose.startSession() const repo = req.ctx.repositories.getBaseUserRepository() - let returnValue // temporary measure to allow tests to work after fixing #920 // tests required changing the global limit to force pagination @@ -35,36 +33,32 @@ async function getAllUsers (req, res, next) { options.sort = { short_name: 'asc' } options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value - try { - returnValue = await repo.getAllUsers(options) - // Hydrate roles - const orgRepo = req.ctx.repositories.getBaseOrgRepository() - const distinctOrgUUIDs = [...new Set(returnValue.users.map(u => u.org_UUID))] - - // Fetch all relevant orgs in one go (or in parallel) if possible, but map is easy for now - // Since we don't have a "getManyOrgsByUUID", we might need to do it one by one or improve repository - // For now, let's iterate and fetch. It's not optimal but safe given repo limitations. - // Optimization: We can build a map of orgUUID -> orgObject - const orgMap = {} - for (const uuid of distinctOrgUUIDs) { - // We need the org content to get admins - const org = await orgRepo.findOneByUUID(uuid) - if (org) { - orgMap[uuid] = org - } + const returnValue = await repo.getAllUsers(options) + // Hydrate roles + const orgRepo = req.ctx.repositories.getBaseOrgRepository() + const distinctOrgUUIDs = [...new Set(returnValue.users.map(u => u.org_UUID))] + + // Fetch all relevant orgs in one go (or in parallel) if possible, but map is easy for now + // Since we don't have a "getManyOrgsByUUID", we might need to do it one by one or improve repository + // For now, let's iterate and fetch. It's not optimal but safe given repo limitations. + // Optimization: We can build a map of orgUUID -> orgObject + const orgMap = {} + for (const uuid of distinctOrgUUIDs) { + // We need the org content to get admins + const org = await orgRepo.findOneByUUID(uuid) + if (org) { + orgMap[uuid] = org } - - returnValue.users.forEach(user => { - const org = orgMap[user.org_UUID] - if (org && org.admins && org.admins.includes(user.UUID)) { - user.role = 'ADMIN' - } - // If not admin, leave as is (undefined or empty or whatever it was) - }) - } finally { - await session.endSession() } + returnValue.users.forEach(user => { + const org = orgMap[user.org_UUID] + if (org && org.admins && org.admins.includes(user.UUID)) { + user.role = 'ADMIN' + } + // If not admin, leave as is (undefined or empty or whatever it was) + }) + logger.info({ uuid: req.ctx.uuid, message: 'The user information was sent to the secretariat user.' }) return res.status(200).json(returnValue) } catch (err) { @@ -143,7 +137,7 @@ async function getUser (req, res, next) { } async function createUser (req, res, next) { - const session = await mongoose.startSession() + const session = await mongoose.startSession({ causalConsistency: false }) try { const orgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getBaseUserRepository() diff --git a/src/controller/review-object.controller/index.js b/src/controller/review-object.controller/index.js index 190db6695..702da7529 100644 --- a/src/controller/review-object.controller/index.js +++ b/src/controller/review-object.controller/index.js @@ -324,7 +324,7 @@ router.get('/review/org/:identifier/reviews', ) // Update a review object -router.put('/review/org/:uuid', +router.put('/review/:uuid', /* #swagger.tags = ['Review Object'] #swagger.operationId = 'updateReviewObjectByReviewUUID' @@ -407,7 +407,7 @@ router.put('/review/org/:uuid', ) // Approve a review object -router.put('/review/org/:uuid/approve', +router.put('/review/:uuid/approve', /* #swagger.tags = ['Review Object'] #swagger.operationId = 'approveReviewObject' @@ -491,7 +491,7 @@ router.put('/review/org/:uuid/approve', ) // Reject a review object -router.put('/review/org/:uuid/reject', +router.put('/review/:uuid/reject', /* #swagger.tags = ['Review Object'] #swagger.operationId = 'rejectReviewObject' diff --git a/src/controller/review-object.controller/review-object.controller.js b/src/controller/review-object.controller/review-object.controller.js index 7795f0219..2f1700d38 100644 --- a/src/controller/review-object.controller/review-object.controller.js +++ b/src/controller/review-object.controller/review-object.controller.js @@ -22,9 +22,9 @@ async function getReviewObjectByOrgIdentifier (req, res, next) { let value // We may want this to be something different, but for now we are just testing if (identifierIsUUID) { - value = await repo.getOrgReviewObjectByOrgUUID(identifier, isSecretariat) + value = await repo.getOrgReviewObjectByOrgUUID(identifier, isSecretariat, {}) } else { - value = await repo.getOrgReviewObjectByOrgShortname(identifier, isSecretariat) + value = await repo.getOrgReviewObjectByOrgShortname(identifier, isSecretariat, {}) } if (!value) { return res.status(404).json({ message: 'No pending review object exists for this organization' }) @@ -61,19 +61,26 @@ async function approveReviewObject (req, res, next) { const reviewRepo = req.ctx.repositories.getReviewObjectRepository() const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getBaseUserRepository() + const isSecretariat = await baseOrgRepo.isSecretariatByShortName(req.ctx.org) + const isPendingReview = true const UUID = req.params.uuid const body = req.body - const session = await mongoose.startSession() - let reviewObj + const session = await mongoose.startSession({ causalConsistency: false }) let updatedOrgObj try { session.startTransaction() - const reviewObject = await reviewRepo.findOneByUUID(UUID, { session }) + const bodyValidation = (body && Object.keys(body).length) ? baseOrgRepo.validateOrg(body) : { isValid: true } + if (!bodyValidation.isValid) { + await session.abortTransaction() + return res.status(400).json({ message: 'Invalid body parameters', errors: bodyValidation.errors }) + } + + const reviewObject = await reviewRepo.findOneByUUIDWithConversation(UUID, isSecretariat, isPendingReview, { session }) if (!reviewObject) { await session.abortTransaction() - return res.status(404).json({ message: `No review object found with UUID ${UUID}` }) + return res.status(404).json({ message: `No pending review object found with UUID ${UUID}` }) } const org = await baseOrgRepo.findOneByUUID(reviewObject.target_object_uuid, { session }) @@ -88,25 +95,26 @@ async function approveReviewObject (req, res, next) { const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session }) - reviewObj = await reviewRepo.approveReviewOrgObject(UUID, { session }) - await baseOrgRepo.updateOrgFull(org.short_name, dataToUpdate, { session }, false, requestingUserUUID, false, true) + const reviewObj = await reviewRepo.approveReviewOrgObject(UUID, { session }) + if (!reviewObj) { + await session.abortTransaction() + return res.status(404).json({ message: `Review object not approved with UUID ${UUID}` }) + } + updatedOrgObj = await baseOrgRepo.updateOrgFull(org.short_name, dataToUpdate, { session }, false, requestingUserUUID, false, true) + if (!updatedOrgObj) { + await session.abortTransaction() + return res.status(404).json({ message: `Org Object not updated with UUID ${UUID}` }) + } await session.commitTransaction() - - // Return the updated organization - updatedOrgObj = await baseOrgRepo.findOneByUUID(reviewObject.target_object_uuid) } catch (updateErr) { await session.abortTransaction() - throw updateErr + return res.status(500).json({ message: updateErr.message || 'Failed to approve review object' }) } finally { await session.endSession() } - - if (!reviewObj) { - return res.status(404).json({ message: `No review object found with UUID ${UUID}` }) - } - - return res.status(200).json(updatedOrgObj ? updatedOrgObj.toObject() : null) + _.unset(updatedOrgObj, 'joint_approval_required') + return res.status(200).json(updatedOrgObj) } async function updateReviewObjectByReviewUUID (req, res, next) { @@ -114,30 +122,62 @@ async function updateReviewObjectByReviewUUID (req, res, next) { const UUID = req.params.uuid const orgRepo = req.ctx.repositories.getBaseOrgRepository() const body = req.body + const session = await mongoose.startSession({ causalConsistency: false }) + let updatedReviewObj const result = orgRepo.validateOrg(body) if (!result.isValid) { return res.status(400).json({ message: 'Invalid new_review_data', errors: result.errors }) } - const value = await repo.updateReviewOrgObject(body, UUID) + try { + session.startTransaction() + const reviewObject = await repo.findOneByUUIDWithConversation(UUID, false, true, { session }) + if (!reviewObject) { + await session.abortTransaction() + return res.status(404).json({ message: `No pending review object found with UUID ${UUID}` }) + } + updatedReviewObj = await repo.updateReviewOrgObject(body, UUID, { session }) + } catch (updateErr) { + await session.abortTransaction() + return res.status(500).json({ message: updateErr.message || 'Failed to update review object' }) + } finally { + await session.endSession() + } - if (!value) { + if (!updatedReviewObj) { return res.status(404).json({ message: `No review object found with UUID ${UUID}` }) } - return res.status(200).json(value) + return res.status(200).json(updatedReviewObj) } async function createReviewObject (req, res, next) { + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const repo = req.ctx.repositories.getReviewObjectRepository() const body = req.body + const session = await mongoose.startSession({ causalConsistency: false }) + let createdReviewObj - const value = await repo.createReviewOrgObject(body) + try { + session.startTransaction() + const bodyValidation = (body && Object.keys(body).length) ? baseOrgRepo.validateOrg(body, { session }) : { isValid: false } + if (!bodyValidation.isValid) { + await session.abortTransaction() + return res.status(400).json({ message: 'Invalid body parameters', errors: bodyValidation.errors }) + } + createdReviewObj = await repo.createReviewOrgObject(body, { session }) + await session.commitTransaction() + } catch (createErr) { + await session.abortTransaction() + return res.status(500).json({ message: createErr.message || 'Failed to create review object' }) + } finally { + await session.endSession() + } - if (!value) { + if (!createdReviewObj) { return res.status(500).json({ message: 'Failed to create review object' }) } - return res.status(200).json(value) + return res.status(200).json(createdReviewObj) } /** @@ -173,19 +213,30 @@ async function getReviewHistoryByOrgShortNamePaginated (req, res, next) { } async function rejectReviewObject (req, res, next) { - const repo = req.ctx.repositories.getReviewObjectRepository() + const reviewRepo = req.ctx.repositories.getReviewObjectRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const UUID = req.params.uuid - const session = await mongoose.startSession() + const session = await mongoose.startSession({ causalConsistency: false }) + + const isSecretariat = await baseOrgRepo.isSecretariatByShortName(req.ctx.org, { session }) + + const isPendingReview = true let value try { session.startTransaction() - value = await repo.rejectReviewOrgObject(UUID, { session }) + const reviewObject = await reviewRepo.findOneByUUIDWithConversation(UUID, isSecretariat, isPendingReview, { session }) + if (!reviewObject) { + await session.abortTransaction() + return res.status(404).json({ message: `No pending review object found with UUID ${UUID}` }) + } + + value = await reviewRepo.rejectReviewOrgObject(UUID, { session }) await session.commitTransaction() } catch (rejectErr) { await session.abortTransaction() - throw rejectErr + return res.status(500).json({ message: rejectErr.message || 'Failed to reject review object' }) } finally { await session.endSession() } diff --git a/src/repositories/auditRepository.js b/src/repositories/auditRepository.js index f6f23ec44..05e29b453 100644 --- a/src/repositories/auditRepository.js +++ b/src/repositories/auditRepository.js @@ -27,7 +27,7 @@ class AuditRepository extends BaseRepository { try { // Try to find existing document - let audit = await this.findOneByTargetUUID(targetUUID, { options }) + let audit = await this.findOneByTargetUUID(targetUUID, options) if (!audit) { // Create new document if doesn't exist // Assuming 'uuid' is available for generating a new UUID @@ -41,7 +41,7 @@ class AuditRepository extends BaseRepository { audit.history.push(historyEntry) } - await audit.save({ options }) + await audit.save(options) return audit.toObject() } catch (error) { throw new Error('Failed to save audit history entry.') diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index 17eadc711..eea3a8230 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -331,7 +331,6 @@ class BaseOrgRepository extends BaseRepository { // In the future we may be able to dynamically detect, but for now we will take a boolean let legacyObjectRaw = null let registryObjectRaw = null - let legacyObject = null let registryObject = null const legacyOrgRepo = new OrgRepository() const ReviewObjectRepository = require('./reviewObjectRepository') @@ -377,7 +376,7 @@ class BaseOrgRepository extends BaseRepository { if (isSecretariat) { registryObject = await SecretariatObjectToSave.save(options) } else { - await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, { options }) + await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options) } } else if (registryObjectRaw.authority.includes('CNA')) { // A special case, we should make sure we have the default quota if it is not set @@ -391,7 +390,7 @@ class BaseOrgRepository extends BaseRepository { if (isSecretariat) { registryObject = await CNAObjectToSave.save(options) } else { - await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, { options }) + await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options) } } else if (registryObjectRaw.authority.includes('ADP')) { registryObjectRaw.hard_quota = 0 @@ -399,7 +398,7 @@ class BaseOrgRepository extends BaseRepository { if (isSecretariat) { registryObject = await adpObjectToSave.save(options) } else { - await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, { options }) + await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options) } } else if (registryObjectRaw.authority.includes('BULK_DOWNLOAD')) { registryObjectRaw.hard_quota = 0 @@ -407,7 +406,7 @@ class BaseOrgRepository extends BaseRepository { if (isSecretariat) { registryObject = await bulkDownloadObjectToSave.save(options) } else { - await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, { options }) + await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options) } } else { // Throw an Error instance so callers can catch and handle it properly @@ -448,8 +447,9 @@ class BaseOrgRepository extends BaseRepository { // The legacy way of doing this, the way this is written under the hood there is no other way // This await does not return a value, even though there is a return in it. :shrugg: + let postUpdate = {} if (isSecretariat) { - await legacyOrgRepo.updateByOrgUUID(sharedUUID, legacyObjectRaw, options) + postUpdate = await legacyOrgRepo.updateByOrgUUID(sharedUUID, legacyObjectRaw, options) } // If we are not a secretariat, then we need to return the uuid of the review object. @@ -459,12 +459,9 @@ class BaseOrgRepository extends BaseRepository { if (isLegacyObject) { // This gets us the mongoose object that has all the right data in it, the "legacyObjectRaw" is the custom JSON we are sending. NOT the post written object. - legacyObject = await legacyOrgRepo.findOneByShortName( - legacyObjectRaw.short_name, - options - ) // Convert the actual model, back to a json model - const legacyObjectRawJson = legacyObject.toObject() + + const legacyObjectRawJson = postUpdate.toObject() // Remove private stuff delete legacyObjectRawJson.__v delete legacyObjectRawJson._id @@ -658,8 +655,8 @@ class BaseOrgRepository extends BaseRepository { } // Save changes - await legacyOrg.save({ options }) - await registryOrg.save({ options }) + await legacyOrg.save(options) + await registryOrg.save(options) if (isLegacyObject) { const plainJavascriptLegacyOrg = legacyOrg.toObject() delete plainJavascriptLegacyOrg.__v @@ -772,24 +769,24 @@ class BaseOrgRepository extends BaseRepository { // write the joint approval to the database jointApprovalRegistry = _.merge({}, registryOrg.toObject(), registryObjectRaw) if (reviewObject) { - await reviewObjectRepo.updateReviewOrgObject(jointApprovalRegistry, reviewObject.uuid, { options }) + await reviewObjectRepo.updateReviewOrgObject(jointApprovalRegistry, reviewObject.uuid, options) } else { - await reviewObjectRepo.createReviewOrgObject(jointApprovalRegistry, { options }) + await reviewObjectRepo.createReviewOrgObject(jointApprovalRegistry, options) } } else { // If no changes between org and new object but a review object exists, remove it since joint approval is no longer needed if (reviewObject) { - await reviewObjectRepo.rejectReviewOrgObject(reviewObject.uuid, { options }) + await reviewObjectRepo.rejectReviewOrgObject(reviewObject.uuid, options) } } updatedRegistryOrg = _.merge(registryOrg, _.omit(registryObjectRaw, jointApprovalFieldsRegistry)) updatedLegacyOrg = _.merge(legacyOrg, _.omit(legacyObjectRaw, jointApprovalFieldsLegacy)) } - // handle conversation const requestingUser = await userRepo.findUserByUUID(requestingUserUUID, options) + const conversationArray = [] if (conversation) { - await conversationRepo.createConversation(registryOrg.UUID, conversation, requestingUser, isSecretariat, { options }) + conversationArray.push(await conversationRepo.createConversation(registryOrg.UUID, conversation, requestingUser, isSecretariat, options)) } // ADD AUDIT ENTRY AUTOMATICALLY for the registry object before it gets saved. @@ -826,6 +823,7 @@ class BaseOrgRepository extends BaseRepository { } console.log('Audit entry created for registry object') } catch (auditError) { + console.error('Audit entry creation failed:', auditError) } } @@ -878,6 +876,7 @@ class BaseOrgRepository extends BaseRepository { } const plainJavascriptRegistryOrg = updatedRegistryOrg.toObject() + plainJavascriptRegistryOrg.conversation = conversationArray // Remove private things delete plainJavascriptRegistryOrg.__v delete plainJavascriptRegistryOrg._id @@ -1055,7 +1054,7 @@ class BaseOrgRepository extends BaseRepository { }, time: { created: registryOrg?.created ?? null, - modified: registryOrg?.modified ?? null + modified: registryOrg?.last_updated ?? null } } } diff --git a/src/repositories/baseRepository.js b/src/repositories/baseRepository.js index 1a179d157..046a1ad77 100644 --- a/src/repositories/baseRepository.js +++ b/src/repositories/baseRepository.js @@ -59,7 +59,7 @@ class BaseRepository { } async findOneAndUpdate (query = {}, set = {}, options = {}) { - return this.collection.findOneAndUpdate(query, set, options) + return this.collection.findOneAndUpdate(query, set, { ...options, new: true }) } async findOneAndReplace (query = {}, replacement = {}) { diff --git a/src/repositories/baseUserRepository.js b/src/repositories/baseUserRepository.js index e9074d898..8de004261 100644 --- a/src/repositories/baseUserRepository.js +++ b/src/repositories/baseUserRepository.js @@ -209,7 +209,7 @@ class BaseUserRepository extends BaseRepository { if (Array.isArray(org.admins)) { org.admins = org.admins.filter(a => a !== uuid) } - await org.save({ options }) + await org.save(options) } return deleteResult.deletedCount @@ -435,12 +435,13 @@ class BaseUserRepository extends BaseRepository { if (rolesToRemove.includes('ADMIN')) { const filteredUuids = registryOrg.admins.filter(uuid => uuid !== registryUser.UUID) registryOrg.admins = filteredUuids + await registryOrg.save(options) } if (rolesToAdd.includes('ADMIN') && !incomingParameters?.org_short_name) { const orgUpdates = await baseOrgRepository.getOrgObject(orgShortname) orgUpdates.admins = [..._.get(orgUpdates, 'admins', []), registryUser.UUID] - await orgUpdates.save({ options }) + await orgUpdates.save(options) } const initialRoles = legacyUser.authority?.active_roles ?? [] @@ -461,12 +462,12 @@ class BaseUserRepository extends BaseRepository { } legacyUser.org_UUID = newOrg.UUID - await registryOrg.save({ options }) - await newOrg.save({ options }) + await registryOrg.save(options) + await newOrg.save(options) } - await legacyUser.save({ options }) - await registryUser.save({ options }) + await legacyUser.save(options) + await registryUser.save(options) if (!isRegistryObject) { const plainJavascriptLegacyUser = legacyUser.toObject() @@ -516,7 +517,7 @@ class BaseUserRepository extends BaseRepository { if (!isRegistryObject) { legacyObjectRaw = incomingUser - registryObjectRaw = this.convertRegistryToLegacy(incomingUser) + registryObjectRaw = this.convertLegacyToRegistry(incomingUser) } else { registryObjectRaw = incomingUser legacyObjectRaw = this.convertRegistryToLegacy(incomingUser) @@ -560,12 +561,12 @@ class BaseUserRepository extends BaseRepository { updatedLegacyUser.org_UUID = newOrg.UUID // Save org changes - await currentOrg.save({ options }) - await newOrg.save({ options }) + await currentOrg.save(options) + await newOrg.save(options) } - await updatedLegacyUser.save({ options }) - await updatedRegistryUser.save({ options }) + await updatedLegacyUser.save(options) + await updatedRegistryUser.save(options) } catch (error) { throw new Error('Failed to update user: ' + error.message) } @@ -610,8 +611,8 @@ class BaseUserRepository extends BaseRepository { const secret = await argon2.hash(randomKey) legUser.secret = secret regUser.secret = secret - await legUser.save({ options }) - await regUser.save({ options }) + await legUser.save(options) + await regUser.save(options) return randomKey } @@ -667,7 +668,7 @@ class BaseUserRepository extends BaseRepository { active: registryUser.status === 'active', time: { created: registryUser?.created ?? null, - modified: registryUser?.modified ?? null + modified: registryUser?.last_updated ?? null } } } diff --git a/src/repositories/conversationRepository.js b/src/repositories/conversationRepository.js index 1558d0d15..9d7fe0849 100644 --- a/src/repositories/conversationRepository.js +++ b/src/repositories/conversationRepository.js @@ -47,7 +47,7 @@ class ConversationRepository extends BaseRepository { const latestConversation = await ConversationModel.findOne({ target_uuid: targetUUID, next_conversation_uuid: null }, null, options) if (latestConversation) { latestConversation.next_conversation_uuid = newUUID - await latestConversation.save({ options }) + await latestConversation.save(options) } const conversationObj = { UUID: newUUID, diff --git a/src/repositories/orgRepository.js b/src/repositories/orgRepository.js index 1940f4695..099cc2fa8 100644 --- a/src/repositories/orgRepository.js +++ b/src/repositories/orgRepository.js @@ -24,7 +24,8 @@ class OrgRepository extends BaseRepository { // The filter to find the document const filter = { UUID: orgUUID } const updatePayload = { $set: updateData } - return this.collection.findOneAndUpdate(filter, updatePayload, executeOptions) + const data = await this.collection.findOneAndUpdate(filter, updatePayload, { ...executeOptions, new: true }) + return data } async isSecretariat (org, options = {}) { diff --git a/src/repositories/reviewObjectRepository.js b/src/repositories/reviewObjectRepository.js index 24819c7d7..8ebcda1f6 100644 --- a/src/repositories/reviewObjectRepository.js +++ b/src/repositories/reviewObjectRepository.js @@ -24,11 +24,12 @@ class ReviewObjectRepository extends BaseRepository { return reviewObject || null } - async findOneByUUIDWithConversation (UUID, isSecretariat, options = {}) { + async findOneByUUIDWithConversation (UUID, isSecretariat, pending = false, options = {}) { const ConversationRepository = require('./conversationRepository') const conversationRepository = new ConversationRepository() let reviewObject - const reviewObjectRaw = await ReviewObjectModel.findOne({ uuid: UUID }, null, options) + const query = pending ? { uuid: UUID, status: 'pending' } : { uuid: UUID } + const reviewObjectRaw = await ReviewObjectModel.findOne(query, null, options) if (reviewObjectRaw) { reviewObject = reviewObjectRaw.toObject() const conversations = await conversationRepository.getAllByTargetUUID(reviewObject.target_object_uuid, isSecretariat, options) @@ -146,7 +147,7 @@ class ReviewObjectRepository extends BaseRepository { } const reviewObject = new ReviewObjectModel(reviewObjectRaw) - await reviewObject.save({ options }) + await reviewObject.save(options) return reviewObject.toObject() } @@ -159,7 +160,7 @@ class ReviewObjectRepository extends BaseRepository { reviewObject.new_review_data = body - const result = await reviewObject.save({ options }) + const result = await reviewObject.save(options) return result.toObject() } diff --git a/src/scripts/populate.js b/src/scripts/populate.js index 738143127..70553e1c7 100644 --- a/src/scripts/populate.js +++ b/src/scripts/populate.js @@ -132,15 +132,24 @@ db.once('open', async () => { // don't close database connection until all remaining populate // promises are resolved - Promise.all(populatePromises).then(function () { + Promise.all(populatePromises).then(async function () { logger.info('Successfully populated the database!') + const indexPromises = [] Object.keys(indexesToCreate).forEach(col => { indexesToCreate[col].forEach(index => { - db.collections[col].createIndex(index) + indexPromises.push(db.collections[col].createIndex(index)) }) }) - mongoose.connection.close() + + try { + await Promise.all(indexPromises) + logger.info('Successfully created indexes!') + } catch (err) { + logger.error('Error creating indexes:', err) + } finally { + mongoose.connection.close() + } }) } else { mongoose.connection.close() diff --git a/src/utils/utils.js b/src/utils/utils.js index 3ef47e2a9..355f5178e 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -125,7 +125,7 @@ async function isAdmin (requesterUsername, requesterShortName, isRegistry = fals if (user) { if (isRegistry) { - result = baseUserRepository.isAdmin(requesterShortName, requesterUsername, options) + result = baseUserRepository.isAdmin(requesterUsername, requesterShortName, options) } else { result = user.authority.active_roles.includes(CONSTANTS.USER_ROLE_ENUM.ADMIN) } @@ -147,7 +147,7 @@ async function isAdminUUID (requesterUsername, requesterOrgUUID, isRegistry = fa if (user && orgObject) { if (isRegistry) { - result = baseUserRepository.isAdmin(orgObject.short_name, requesterUsername, options) + result = baseUserRepository.isAdmin(requesterUsername, orgObject.short_name, options) } else { result = user.authority.active_roles.includes(CONSTANTS.USER_ROLE_ENUM.ADMIN) } diff --git a/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js b/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js index e3eabff16..64d98ea9f 100644 --- a/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js +++ b/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js @@ -148,9 +148,9 @@ describe('Testing Joint approval', () => { }) }) it('Secretariat can approve the ORG review with body parameter', async function () { - const newBody = { short_name: 'final_non_secretariat_org', hard_quota: 20000 } + const newBody = { short_name: 'final_non_secretariat_org', hard_quota: 20000, long_name: 'Final Non Secretariat Organization' } await chai.request(app) - .put(`/api/review/org/${reviewUUID}/approve`) + .put(`/api/review/${reviewUUID}/approve`) .set(secretariatHeaders) .send(newBody) .then((res) => { @@ -306,7 +306,7 @@ describe('Testing Joint approval', () => { }) it('Secretariat can approve the ORG review', async function () { await chai.request(app) - .put(`/api/review/org/${reviewUUID}/approve`) + .put(`/api/review/${reviewUUID}/approve`) .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(200) diff --git a/test/integration-tests/review-object/review-object.repository.test.js b/test/integration-tests/review-object/review-object.repository.test.js new file mode 100644 index 000000000..197d3640d --- /dev/null +++ b/test/integration-tests/review-object/review-object.repository.test.js @@ -0,0 +1,99 @@ +/* eslint-disable no-unused-expressions */ +const chai = require('chai') +const expect = chai.expect +const ReviewObjectRepository = require('../../../src/repositories/reviewObjectRepository') +const ReviewObjectModel = require('../../../src/model/reviewobject') + +describe('ReviewObjectRepository Tests', function () { + let repository + let testUUID + + before(async () => { + repository = new ReviewObjectRepository() + + // Create a test review object with pending status + const pendingReview = new ReviewObjectModel({ + uuid: 'pending-test-uuid-123', + target_object_uuid: 'target-uuid-456', + status: 'pending', + new_review_data: { short_name: 'pending_org' } + }) + await pendingReview.save() + + // Create a test review object with approved status + const approvedReview = new ReviewObjectModel({ + uuid: 'approved-test-uuid-456', + target_object_uuid: 'target-uuid-789', + status: 'approved', + new_review_data: { short_name: 'approved_org' } + }) + await approvedReview.save() + + testUUID = 'pending-test-uuid-123' + }) + + after(async () => { + // Clean up test data + await ReviewObjectModel.deleteMany({ + uuid: { $in: ['pending-test-uuid-123', 'approved-test-uuid-456'] } + }) + }) + + describe('findOneByUUIDWithConversation', function () { + it('should return the pending review object when pending=true', async () => { + const result = await repository.findOneByUUIDWithConversation(testUUID, true, true) + + expect(result).to.exist + expect(result.uuid).to.equal(testUUID) + expect(result.status).to.equal('pending') + }) + + it('should return null when review object not found with pending=true', async () => { + const nonExistentUUID = 'non-existent-uuid-999' + const result = await repository.findOneByUUIDWithConversation(nonExistentUUID, true, true) + + expect(result).to.be.null + }) + + it('should return the approved review object when pending=false', async () => { + const approvedUUID = 'approved-test-uuid-456' + const result = await repository.findOneByUUIDWithConversation(approvedUUID, true, false) + + expect(result).to.exist + expect(result.uuid).to.equal(approvedUUID) + expect(result.status).to.equal('approved') + }) + + it('should return the pending review object when pending=false', async () => { + const result = await repository.findOneByUUIDWithConversation(testUUID, true, false) + + expect(result).to.exist + expect(result.uuid).to.equal(testUUID) + expect(result.status).to.equal('pending') + }) + + it('should return the pending review object with default pending value', async () => { + const result = await repository.findOneByUUIDWithConversation(testUUID, true) + + expect(result).to.exist + expect(result.uuid).to.equal(testUUID) + expect(result.status).to.equal('pending') + }) + + it('should return the approved review object with default pending value', async () => { + const approvedUUID = 'approved-test-uuid-456' + const result = await repository.findOneByUUIDWithConversation(approvedUUID, true) + + expect(result).to.exist + expect(result.uuid).to.equal(approvedUUID) + expect(result.status).to.equal('approved') + }) + + it('should not return approved review object when pending=true', async () => { + const approvedUUID = 'approved-test-uuid-456' + const result = await repository.findOneByUUIDWithConversation(approvedUUID, true, true) + + expect(result).to.be.null + }) + }) +}) diff --git a/test/integration-tests/review-object/reviewObjectTest.js b/test/integration-tests/review-object/reviewObjectTest.js index 76d3457b5..81958856f 100644 --- a/test/integration-tests/review-object/reviewObjectTest.js +++ b/test/integration-tests/review-object/reviewObjectTest.js @@ -39,6 +39,7 @@ describe('Review Object Controller Integration Tests', () => { expect(res.body).to.have.property('uuid') expect(res.body).to.have.property('target_object_uuid', orgUUID) expect(res.body).to.have.property('new_review_data') + expect(res.body.status).to.equal('pending') reviewUUID = res.body.uuid }) @@ -88,7 +89,7 @@ describe('Review Object Controller Integration Tests', () => { reviewObject.short_name = 'updated_org' const res = await chai .request(app) - .put(`/api/review/org/${reviewUUID}`) + .put(`/api/review/${reviewUUID}`) .set({ ...constants.headers }) .send(reviewObject) expect(res).to.have.status(200) @@ -210,7 +211,7 @@ describe('Review Object Controller Integration Tests', () => { it('Approves a review object and updates the organization', async () => { const res = await chai .request(app) - .put(`/api/review/org/${approveTestReviewUUID}/approve`) + .put(`/api/review/${approveTestReviewUUID}/approve`) .set({ ...constants.headers }) .send({}) expect(res).to.have.status(200) @@ -253,7 +254,7 @@ describe('Review Object Controller Integration Tests', () => { it('Rejects a review object', async () => { const res = await chai .request(app) - .put(`/api/review/org/${rejectTestReviewUUID}/reject`) + .put(`/api/review/${rejectTestReviewUUID}/reject`) .set({ ...constants.headers }) .send({}) expect(res).to.have.status(200) @@ -393,22 +394,22 @@ describe('Review Object Controller Integration Tests', () => { const fakeUUID = '00000000-0000-0000-0000-000000000000' const res = await chai .request(app) - .put(`/api/review/org/${fakeUUID}/approve`) + .put(`/api/review/${fakeUUID}/approve`) .set({ ...constants.headers }) .send({}) expect(res).to.have.status(404) - expect(res.body.message).to.equal(`No review object found with UUID ${fakeUUID}`) + expect(res.body.message).to.equal(`No pending review object found with UUID ${fakeUUID}`) }) it('Returns 404 when rejecting non-existent review object', async () => { const fakeUUID = '00000000-0000-0000-0000-000000000000' const res = await chai .request(app) - .put(`/api/review/org/${fakeUUID}/reject`) + .put(`/api/review/${fakeUUID}/reject`) .set({ ...constants.headers }) .send({}) expect(res).to.have.status(404) - expect(res.body.message).to.equal(`No review object found with UUID ${fakeUUID}`) + expect(res.body.message).to.equal(`No pending review object found with UUID ${fakeUUID}`) }) it('Returns 404 when updating non-existent review object', async () => { @@ -459,7 +460,7 @@ describe('Review Object Controller Integration Tests', () => { it('Non-secretariat user cannot update review object', async () => { const res = await chai .request(app) - .put(`/api/review/org/${reviewUUID}`) + .put(`/api/review/${reviewUUID}`) .set({ ...constants.nonSecretariatUserHeaders }) .send({ short_name: 'test' }) expect(res).to.have.status(403) @@ -468,7 +469,7 @@ describe('Review Object Controller Integration Tests', () => { it('Non-secretariat user cannot approve review object', async () => { const res = await chai .request(app) - .put(`/api/review/org/${reviewUUID}/approve`) + .put(`/api/review/${reviewUUID}/approve`) .set({ ...constants.nonSecretariatUserHeaders }) .send({}) expect(res).to.have.status(403) @@ -477,7 +478,7 @@ describe('Review Object Controller Integration Tests', () => { it('Non-secretariat user cannot reject review object', async () => { const res = await chai .request(app) - .put(`/api/review/org/${reviewUUID}/reject`) + .put(`/api/review/${reviewUUID}/reject`) .set({ ...constants.nonSecretariatUserHeaders }) .send({}) expect(res).to.have.status(403) diff --git a/test/unit-tests/org/orgCreateTest.js b/test/unit-tests/org/orgCreateTest.js index cf098cdcb..9b9485ffd 100644 --- a/test/unit-tests/org/orgCreateTest.js +++ b/test/unit-tests/org/orgCreateTest.js @@ -196,8 +196,6 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { beforeEach(() => { sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(null) - updateOrgStub = sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(true) - aggregateOrgStub = sinon.stub(orgRepo, 'aggregate') aggregateRegOrgStub = sinon.stub(baseOrgRepo, 'aggregate') @@ -224,6 +222,7 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { body: testOrgPayload } } + sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(fakeMongooseDocument) sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(fakeMongooseDocument) await orgController.ORG_CREATE_SINGLE(req, res, next) @@ -250,6 +249,7 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { body: testOrgPayload } } + sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(fakeMongooseDocument) sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(fakeMongooseDocument) await orgController.ORG_CREATE_SINGLE(req, res, next) @@ -278,9 +278,11 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { body: testOrgPayload } } + const test = await new Org(fakeLegacySavedObjectCisco) sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(await new Org(fakeLegacySavedObjectCisco)) sinon.stub(CNAOrgModel.prototype, 'save').resolves(fakeLegacySavedObjectCisco) + sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -308,8 +310,10 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { body: testOrgPayload } } - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(await new Org(expectedCreatedOrg)) + const test = await new Org(expectedCreatedOrg) + sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(test) sinon.stub(CNAOrgModel.prototype, 'save').resolves(expectedCreatedOrg) + sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -335,9 +339,10 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { body: testOrgPayload } } - - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(await new Org(expectedCreatedOrg)) + const test = await new Org(expectedCreatedOrg) + sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(test) sinon.stub(CNAOrgModel.prototype, 'save').resolves(expectedCreatedOrg) + sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -359,9 +364,10 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { body: testOrgPayload } } - - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(await new Org(testOrgPayload)) + const test = await new Org({ ...testOrgPayload, policies: { id_quota: 0 } }) + sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(test) sinon.stub(ADPOrgModel.prototype, 'save').resolves(testOrgPayload) + updateOrgStub = sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) diff --git a/test/unit-tests/review-object/review-object.controller.test.js b/test/unit-tests/review-object/review-object.controller.test.js index ba9a48eee..45f4f779c 100644 --- a/test/unit-tests/review-object/review-object.controller.test.js +++ b/test/unit-tests/review-object/review-object.controller.test.js @@ -163,11 +163,11 @@ describe('Review Object Controller', function () { req.params.uuid = uuid req.body.new_review_data = { foo: 'bar' } orgRepoStub.validateOrg = sinon.stub().returns({ isValid: true }) + repoStub.findOneByUUIDWithConversation = sinon.stub().resolves(null) repoStub.updateReviewOrgObject = sinon.stub().resolves(undefined) await controller.updateReviewObjectByReviewUUID(req, res, next) - expect(repoStub.updateReviewOrgObject.calledWith(req.body, uuid)).to.be.true expect(res.status.calledWith(404)).to.be.true - expect(res.json.calledWith({ message: `No review object found with UUID ${uuid}` })).to.be.true + expect(res.json.calledWith({ message: `No pending review object found with UUID ${uuid}` })).to.be.true }) it('should return 200 with updated value', async () => { @@ -176,6 +176,7 @@ describe('Review Object Controller', function () { req.params.uuid = uuid req.body.new_review_data = { foo: 'bar' } orgRepoStub.validateOrg = sinon.stub().returns({ isValid: true }) + repoStub.findOneByUUIDWithConversation = sinon.stub().resolves(true) repoStub.updateReviewOrgObject = sinon.stub().resolves(updated) await controller.updateReviewObjectByReviewUUID(req, res, next) expect(repoStub.updateReviewOrgObject.calledWith(req.body, uuid)).to.be.true @@ -233,15 +234,17 @@ describe('Review Object Controller', function () { }) it('should return 404 if review object not found', async () => { - repoStub.findOneByUUID = sinon.stub().resolves(null) + orgRepoStub.validateOrg = sinon.stub().returns({ isValid: true }) + repoStub.findOneByUUIDWithConversation = sinon.stub().resolves(null) await controller.approveReviewObject(req, res, next) expect(res.status.calledWith(404)).to.be.true - expect(res.json.calledWith({ message: `No review object found with UUID ${reviewUUID}` })).to.be.true + expect(res.json.calledWith({ message: `No pending review object found with UUID ${reviewUUID}` })).to.be.true expect(sessionStub.abortTransaction.calledOnce).to.be.true }) it('should return 404 if organization not found', async () => { - repoStub.findOneByUUID = sinon.stub().resolves(reviewObject) + orgRepoStub.validateOrg = sinon.stub().returns({ isValid: true }) + repoStub.findOneByUUIDWithConversation = sinon.stub().resolves(reviewObject) orgRepoStub.findOneByUUID = sinon.stub().resolves(null) await controller.approveReviewObject(req, res, next) expect(res.status.calledWith(404)).to.be.true @@ -250,36 +253,41 @@ describe('Review Object Controller', function () { }) it('should approve review object and update organization with review data', async () => { - repoStub.findOneByUUID = sinon.stub().resolves(reviewObject) - orgRepoStub.findOneByUUID = sinon.stub() - .onFirstCall().resolves(orgObj) - .onSecondCall().resolves(updatedOrgObj) + orgRepoStub.validateOrg = sinon.stub().returns({ isValid: true }) + repoStub.findOneByUUIDWithConversation = sinon.stub().resolves(reviewObject) + orgRepoStub.findOneByUUID = sinon.stub().resolves(orgObj) + userRepoStub.getUserUUID = sinon.stub().resolves('user-uuid') repoStub.approveReviewOrgObject = sinon.stub().resolves({ ...reviewObject, status: 'approved' }) orgRepoStub.updateOrgFull = sinon.stub().resolves(updatedOrgObj) - userRepoStub.getUserUUID = sinon.stub().resolves('user-uuid') await controller.approveReviewObject(req, res, next) expect(orgRepoStub.updateOrgFull.calledOnce).to.be.true expect(res.status.calledWith(200)).to.be.true - expect(res.json.calledWith({ short_name: 'updated_org' })).to.be.true + expect(res.json.calledWith(updatedOrgObj)).to.be.true }) }) describe('rejectReviewObject', function () { const reviewUUID = 'review-uuid-123' + const reviewObject = { + uuid: reviewUUID, + new_review_data: { short_name: 'org' } + } beforeEach(() => { req.params.uuid = reviewUUID }) it('should return 404 if review object not found', async () => { + repoStub.findOneByUUIDWithConversation = sinon.stub().resolves(null) repoStub.rejectReviewOrgObject = sinon.stub().resolves(null) await controller.rejectReviewObject(req, res, next) expect(res.status.calledWith(404)).to.be.true - expect(res.json.calledWith({ message: `No review object found with UUID ${reviewUUID}` })).to.be.true + expect(res.json.calledWith({ message: `No pending review object found with UUID ${reviewUUID}` })).to.be.true }) it('should return 200 with rejected review object', async () => { + repoStub.findOneByUUIDWithConversation = sinon.stub().resolves(reviewObject) const rejectedObj = { uuid: reviewUUID, status: 'rejected' } repoStub.rejectReviewOrgObject = sinon.stub().resolves(rejectedObj) await controller.rejectReviewObject(req, res, next)