From d4322d0363dffd42edd2619f159b46d852ef0b20 Mon Sep 17 00:00:00 2001 From: Andrew Foote Date: Thu, 3 Sep 2026 10:45:38 -0400 Subject: [PATCH] fix: restore web search for v1.0.0-beta --- api-docs/openapi.json | 2 +- controllers/searchController.js | 14 ++++++------ controllers/searchController.test.js | 32 +++++++++++++++++++++++++--- package-lock.json | 4 ++-- package.json | 2 +- routes/search.integration.test.js | 4 ++-- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 2a2cffa..b4591de 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "0.5.0-beta", + "version": "1.0.0-beta", "title": "CVE Search API", "description": "CVE Search API to retrieve all matching CVE records after applying request inputs as filters.", "contact": { diff --git a/controllers/searchController.js b/controllers/searchController.js index a8f5a8b..6cd002c 100644 --- a/controllers/searchController.js +++ b/controllers/searchController.js @@ -77,10 +77,10 @@ function getWebSearchSuccessData(searchResponse) { const hits = searchResponse?.data?.hits; const totalValue = hits?.total?.value; + const notes = searchResponse.notes === undefined ? [] : searchResponse.notes; + const responseErrors = searchResponse.errors === undefined ? [] : searchResponse.errors; const hasValidMetadata = - typeof searchResponse.status === 'string' && - Array.isArray(searchResponse.notes) && - Array.isArray(searchResponse.errors); + typeof searchResponse.status === 'string' && Array.isArray(notes) && Array.isArray(responseErrors); if (!hasValidMetadata || !Array.isArray(hits?.hits) || !Number.isSafeInteger(totalValue) || totalValue < 0) { return null; @@ -88,7 +88,9 @@ function getWebSearchSuccessData(searchResponse) { return { hits: hits.hits, - totalValue + totalValue, + notes, + errors: responseErrors }; } @@ -286,8 +288,8 @@ async function webSearchHandler(req, res, next) { if (successData) { status = 200; body.searchMetadata.searchStatus = searchResponse.status; - body.searchMetadata.notes = searchResponse.notes; - body.searchMetadata.errors = searchResponse.errors; + body.searchMetadata.notes = successData.notes; + body.searchMetadata.errors = successData.errors; body.resultsTotal = successData.totalValue; body.data = successData.hits; } else { diff --git a/controllers/searchController.test.js b/controllers/searchController.test.js index ad42d84..f5da845 100644 --- a/controllers/searchController.test.js +++ b/controllers/searchController.test.js @@ -57,7 +57,7 @@ function rawResponse(totalValue, hits = []) { return advancedResponse(totalValue, hits).data; } -function basicResponse({ ok = true, status = 'ok', total = 0, hits = [], notes = [], errors = [] } = {}) { +function basicResponse({ ok = true, status = 'ok', total = 0, hits = [], notes, errors } = {}) { return { status, notes, @@ -1459,8 +1459,6 @@ describe('Search controller WEB_SEARCH handler', () => { test('WEB_SEARCH fails closed when cve-core reports success without a complete hits response', async () => { mockBasicSearch.mockResolvedValue({ status: 'ok', - notes: [], - errors: [], data: {}, isOk: jest.fn(() => true) }); @@ -1496,6 +1494,34 @@ describe('Search controller WEB_SEARCH handler', () => { }); }); + test.each([ + ['notes', 'invalid notes'], + ['errors', { message: 'invalid errors' }] + ])('WEB_SEARCH fails closed when successful cve-core metadata has invalid %s', async (field, value) => { + mockBasicSearch.mockResolvedValue({ + ...basicResponse(), + [field]: value + }); + + await controller.WEB_SEARCH( + { + body: { + query: 'apache' + } + }, + res, + next + ); + + expect(res.status).toHaveBeenCalledWith(502); + expect(res.json.mock.calls[0][0].searchMetadata.errors).toStrictEqual([ + { + error: 'UPSTREAM_SEARCH_ERROR', + message: 'Search backend returned an error. Please try again later.' + } + ]); + }); + test('WEB_SEARCH returns a sanitized error when BasicSearchManager throws', async () => { mockBasicSearch.mockRejectedValue(new Error('boom')); diff --git a/package-lock.json b/package-lock.json index cfad6cc..6c4a65c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cve-search-api", - "version": "0.5.0-beta", + "version": "1.0.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cve-search-api", - "version": "0.5.0-beta", + "version": "1.0.0-beta", "dependencies": { "cors": "^2.8.5", "cve-core": "github:CVEProject/cve-core.git#dev-package-test", diff --git a/package.json b/package.json index 1f07d5b..1027f78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cve-search-api", - "version": "0.5.0-beta", + "version": "1.0.0-beta", "scripts": { "dev": "node routes/swagger.js && nodemon server.js", "coverage": "jest --coverage", diff --git a/routes/search.integration.test.js b/routes/search.integration.test.js index c97c124..73a5219 100644 --- a/routes/search.integration.test.js +++ b/routes/search.integration.test.js @@ -30,8 +30,6 @@ function advancedResponse(totalValue, hits = []) { function basicResponse({ total = 0, hits = [] } = {}) { return { status: 'ok', - notes: [], - errors: [], data: { hits: { total: { @@ -290,6 +288,8 @@ describe('search route integration', () => { from: 5, size: 10 }); + expect(response.body.searchMetadata.notes).toStrictEqual([]); + expect(response.body.searchMetadata.errors).toStrictEqual([]); expect(response.body.resultsTotal).toBe(1); expect(response.body.data).toStrictEqual([{ _id: 'CVE-2024-0002' }]); });