diff --git a/README.md b/README.md index de2d1d1..7bf26a2 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,10 @@ An AWS load balancer can use `/api/ready-check` to stop routing search traffic t `POST /search` accepts an optional JSON object request body and validates supported fields, boolean parameters, pagination values, CVSS severity values, required parameter combinations, CPE Name requirements, and date-time formats. Enum-valued fields are accepted case-insensitively and normalized to their documented canonical values. A non-empty body must use the `application/json` media type with UTF-8 encoding; unsupported media types or encodings return HTTP 415, and malformed JSON returns HTTP 400. If OpenSearch is unavailable, the endpoint returns HTTP 503 with a `Retry-After` header. +Successful `/search` responses use an API-owned response structure rather than exposing search-backend response objects. The top-level fields are `apiVersion`, `status`, `warnings`, `pagination`, and `records`, in that order. `records` contains complete CVE Records directly, without search-backend hit wrappers or infrastructure metadata. `pagination` contains `page`, `pageSize`, `resultsReturned`, `totalResults`, `totalPages`, and `totalIsExact`. Non-fatal conditions are reported in `warnings` before the potentially long `records` array. + +CVE Records do not exist for years before 1999. A syntactically valid pre-1999 value in `cveIds` is therefore a valid search that contributes no matches rather than a request error. The response includes a `CVE_IDS_NOT_RETURNED` warning listing those IDs. When pre-1999 and supported-year IDs are requested together, matching supported-year records are still returned. + Use the optional `scope` field to control which parts of each record participate in matching. Values are case-insensitive: `FULL` (the default) searches base record fields, CNA data, and every ADP provider; `CNA_ONLY` searches base record fields and CNA data; and `ADP_ONLY` searches base record fields and every ADP provider. Scope changes matching only, so successful responses always contain the complete CVE record. CPE applicability filters use CNA data and return HTTP 400 when combined with `ADP_ONLY`. Date filters use the API's full UTC timestamp format and inclusive boundaries. When both boundaries for publication, modification, SSVC timestamp, or KEV date-added filters are supplied, the start must be before or equal to the end. @@ -287,13 +291,13 @@ Each `/search` request owns a lifecycle cancellation signal. When its overall de Two separate 10,000-record controls apply to `/search`: - Every requested page must fall within the first 10,000 results. Direct OpenSearch filters still report an exact total that can exceed 10,000, but pages beyond that window are rejected. -- Filters that require semantic post-filtering inspect at most 10,000 OpenSearch candidates. Their reported total and pagination describe only matches found within that candidate set. +- Filters that require semantic post-filtering inspect at most 10,000 search candidates. Their reported total and pagination describe only matches found within that candidate set. CVSS vector filters, SSVC filters, date-bounded positive `knownExploited` searches, `cpeName`, and `virtualMatchString` searches use semantic post-filtering where OpenSearch cannot guarantee the required same-entry or version-comparison behavior. These searches retrieve up to 10,000 lightweight candidates, apply semantic checks in Node.js, paginate the matching CVE IDs, and fetch the requested page as complete CVE records. Every slash-delimited token supplied to one CVSS vector filter must match a complete token in the same eligible vector; a base token such as `AV:N` does not match a modified token such as `MAV:N`. -The reported total and pagination are complete only within that bounded candidate set; qualifying records beyond the first 10,000 OpenSearch candidates may not be represented. This preserves the existing bounded post-filter behavior until PIT-based pagination is implemented. A `knownExploited` filter without date boundaries uses a direct OpenSearch query and retains ordinary exact-total behavior. +The reported total and pagination are complete only within that bounded candidate set; qualifying records beyond the first 10,000 search candidates may not be represented. When a semantic search reaches that limit, the response sets `pagination.totalIsExact` to `false` and includes a `SEMANTIC_CANDIDATE_LIMIT_REACHED` warning. Semantic searches that remain below the limit and direct searches set `totalIsExact` to `true`. This preserves the existing bounded post-filter behavior until PIT-based pagination is implemented. A `knownExploited` filter without date boundaries uses a direct query and retains ordinary exact-total behavior. -`/search` orders OpenSearch results by CVE ID so repeated shallow-page requests use a deterministic sort. The response `pageSize` preserves the requested `resultsPerPage` value, while `resultsReturned` reports the number of records actually returned and may be smaller for a partial or out-of-range page. A stable sort does not provide a point-in-time snapshot, so index updates between separate page requests can still shift results until PIT-based pagination is implemented. +`/search` orders search results by CVE ID so repeated shallow-page requests use a deterministic sort. The response `pagination.pageSize` preserves the requested `resultsPerPage` value, while `pagination.resultsReturned` reports the number of records actually returned and may be smaller for a partial or out-of-range page. A stable sort does not provide a point-in-time snapshot, so index updates between separate page requests can still shift results until PIT-based pagination is implemented. The API does not cache, coalesce, queue, or rate-limit searches in process. Each request executes its own required OpenSearch operations. Public rate enforcement, admission control, shared caching, and durable metrics collection must be supplied by the deployment infrastructure. The optional aggregate telemetry described above writes diagnostic events to standard output for collection by the deployment logging system. diff --git a/api-docs/openapi.json b/api-docs/openapi.json index b4591de..fa61922 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -21,7 +21,7 @@ "CVE Record" ], "summary": "Retrieves all matching CVE records after applying the JSON request body as filters", - "description": "

Expected Behavior

Retrieves all matching CVE records after applying the JSON request body as filters

Result Counts and Pagination

Direct searches report the exact number of matching records, including totals greater than 10,000, but requested pages must remain within the first 10,000 results.

CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. These searches evaluate at most 10,000 OpenSearch candidates, so their reported totals and pages include only matches found within that candidate set.

", + "description": "

Expected Behavior

Retrieves all matching CVE records after applying the JSON request body as filters

Result Counts and Pagination

Direct searches report the exact number of matching records, including totals greater than 10,000, but requested pages must remain within the first 10,000 results.

CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. These searches evaluate at most 10,000 search candidates, so their reported totals and pages include only matches found within that candidate set. A warning is returned when that limit is reached.

", "operationId": "search", "responses": { "200": { @@ -73,7 +73,7 @@ } }, "502": { - "description": "OpenSearch returned an invalid or unexpected response", + "description": "The search backend returned an invalid or unexpected response", "content": { "application/json": { "schema": { @@ -83,7 +83,7 @@ } }, "503": { - "description": "OpenSearch is unavailable", + "description": "The search service is temporarily unavailable", "content": { "application/json": { "schema": { @@ -93,7 +93,7 @@ }, "headers": { "Retry-After": { - "description": "Seconds before retrying an unavailable OpenSearch dependency", + "description": "Seconds before retrying the unavailable search service", "schema": { "type": "integer" } @@ -101,7 +101,7 @@ } }, "504": { - "description": "Search exceeded its OpenSearch or overall processing deadline", + "description": "Search exceeded its processing deadline", "content": { "application/json": { "schema": { @@ -403,62 +403,39 @@ } ] }, - "SearchHit": { + "SearchWarning": { "type": "object", - "additionalProperties": true, + "additionalProperties": false, "required": [ - "_source" + "code", + "message" ], "properties": { - "_id": { + "code": { "type": "string" }, - "_source": { - "type": "object", - "additionalProperties": true - } - } - }, - "SearchHits": { - "type": "object", - "additionalProperties": true, - "required": [ - "total", - "hits" - ], - "properties": { - "total": { - "type": "object", - "additionalProperties": true, - "required": [ - "value" - ], - "properties": { - "value": { - "type": "integer", - "minimum": 0, - "description": "For direct searches, the exact number of matching records, which may be greater than 10,000. For semantic post-filtered searches, the number of matches found within the bounded 10,000-candidate set." - } - } + "message": { + "type": "string" }, - "hits": { + "cveIds": { "type": "array", + "description": "Requested CVE IDs associated with this warning, when applicable.", "items": { - "$ref": "#/components/schemas/SearchHit" + "type": "string" } } } }, - "SearchResponse": { + "SearchPagination": { "type": "object", "additionalProperties": false, "required": [ "page", "pageSize", "resultsReturned", + "totalResults", "totalPages", - "status", - "data" + "totalIsExact" ], "properties": { "page": { @@ -476,10 +453,44 @@ "minimum": 0, "description": "Number of CVE records actually returned in this response." }, + "totalResults": { + "type": "integer", + "minimum": 0, + "description": "For direct searches, the exact number of matching records, which may be greater than 10,000. For semantic post-filtered searches, the number of matches found within the bounded 10,000-candidate set." + }, "totalPages": { "type": "integer", "minimum": 0, - "description": "Pages derived from the reported total and requested page size. Semantic post-filtered searches derive this value from matches found within the bounded 10,000-candidate set." + "description": "Pages derived from totalResults and pageSize. Semantic post-filtered searches derive this value from matches found within the bounded 10,000-candidate set." + }, + "totalIsExact": { + "type": "boolean", + "description": "True when totalResults represents every match. False when a semantic search reaches the 10,000-candidate limit and additional matches may exist." + } + } + }, + "CveRecord": { + "type": "object", + "additionalProperties": true, + "description": "A complete CVE Record without search-backend transport metadata." + }, + "SearchResponse": { + "type": "object", + "additionalProperties": false, + "required": [ + "apiVersion", + "status", + "warnings", + "pagination", + "records" + ], + "properties": { + "apiVersion": { + "type": "string", + "enum": [ + "1.0" + ], + "description": "Version of the public search response contract." }, "status": { "type": "string", @@ -487,20 +498,20 @@ "ok" ] }, - "data": { - "type": "object", - "additionalProperties": true, - "required": [ - "hits" - ], - "properties": { - "took": { - "type": "number", - "minimum": 0 - }, - "hits": { - "$ref": "#/components/schemas/SearchHits" - } + "warnings": { + "type": "array", + "description": "Non-fatal conditions that may affect how the results should be interpreted.", + "items": { + "$ref": "#/components/schemas/SearchWarning" + } + }, + "pagination": { + "$ref": "#/components/schemas/SearchPagination" + }, + "records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CveRecord" } } } @@ -552,7 +563,7 @@ }, "SearchRequest": { "type": "object", - "description": "Optional filters and pagination controls for a CVE record search. Enum-valued fields are accepted case-insensitively and normalized to the documented canonical values. When both boundaries of a date range are supplied, the start must be before or equal to the end. Direct searches report exact totals even when they exceed 10,000, although requested pages must remain inside that window. CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. They evaluate at most 10,000 OpenSearch candidates, so reported totals and pages include only matches found within that candidate set.", + "description": "Optional filters and pagination controls for a CVE record search. Enum-valued fields are accepted case-insensitively and normalized to the documented canonical values. When both boundaries of a date range are supplied, the start must be before or equal to the end. Direct searches report exact totals even when they exceed 10,000, although requested pages must remain inside that window. CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. They evaluate at most 10,000 search candidates, so reported totals and pages include only matches found within that candidate set.", "additionalProperties": false, "properties": { "scope": { @@ -591,7 +602,7 @@ "type": "string", "maxLength": 4096, "example": "CVE-2024-1234,CVE-2024-5678", - "description": "Returns one or more CVE records identified by a comma-separated list of CVE IDs. Limited to 100 CVE IDs, 64 characters per ID, and 4096 characters overall." + "description": "Returns one or more CVE records identified by a comma-separated list of CVE IDs. CVE Records do not exist for years before 1999; those IDs produce no matches and are identified by a non-fatal response warning without preventing other requested IDs from matching. Limited to 100 CVE IDs, 64 characters per ID, and 4096 characters overall." }, "noRejected": { "oneOf": [ diff --git a/api-docs/openapi.test.js b/api-docs/openapi.test.js index 88428cf..999dd86 100644 --- a/api-docs/openapi.test.js +++ b/api-docs/openapi.test.js @@ -61,7 +61,7 @@ describe('OpenAPI contract', () => { expect(schema.description).toContain('the start must be before or equal to the end'); expect(schema.description).toContain('Direct searches report exact totals'); expect(schema.description).toContain('semantic post-filtering'); - expect(schema.description).toContain('10,000 OpenSearch candidates'); + expect(schema.description).toContain('10,000 search candidates'); expect(Object.keys(schema.properties)).toStrictEqual(constants.SEARCH_QUERY_PARAMS); Object.values(schema.properties).forEach((property) => { expect(property.description).toEqual(expect.any(String)); @@ -71,6 +71,8 @@ describe('OpenAPI contract', () => { expect(schema.properties.cveIds.description).toContain('comma-separated list of CVE IDs'); expect(schema.properties.cveIds.description).toContain(`${constants.MAX_CVE_IDS} CVE IDs`); expect(schema.properties.cveIds.description).toContain(`${constants.MAX_CVE_ID_LENGTH} characters per ID`); + expect(schema.properties.cveIds.description).toContain(`before ${constants.FIRST_CVE_RECORD_YEAR}`); + expect(schema.properties.cveIds.description).toContain('non-fatal response warning'); expect(schema.properties.keywordSearch.description).toContain( `${constants.MAX_KEYWORD_SEARCH_TOKENS} whitespace-delimited terms` ); @@ -181,9 +183,9 @@ describe('OpenAPI contract', () => { expect(responses['400'].description).toContain('Malformed JSON'); expect(responses['415'].description).toContain('application/json'); expect(responses['502'].description).toContain('invalid or unexpected response'); - expect(responses['503'].description).toContain('OpenSearch'); + expect(responses['503'].description).toContain('search service'); expect(responses['503'].headers['Retry-After']).toStrictEqual({ - description: 'Seconds before retrying an unavailable OpenSearch dependency', + description: 'Seconds before retrying the unavailable search service', schema: { type: 'integer' } @@ -194,23 +196,36 @@ describe('OpenAPI contract', () => { const schemas = openapi.components.schemas; const searchResponse = schemas.SearchResponse; - expect(searchResponse.required).toStrictEqual([ + expect(searchResponse.required).toStrictEqual(['apiVersion', 'status', 'warnings', 'pagination', 'records']); + expect(Object.keys(searchResponse.properties)).toStrictEqual([ + 'apiVersion', + 'status', + 'warnings', + 'pagination', + 'records' + ]); + expect(searchResponse.properties.apiVersion.enum).toStrictEqual(['1.0']); + expect(searchResponse.properties.warnings.items.$ref).toBe('#/components/schemas/SearchWarning'); + expect(searchResponse.properties.pagination.$ref).toBe('#/components/schemas/SearchPagination'); + expect(searchResponse.properties.records.items.$ref).toBe('#/components/schemas/CveRecord'); + expect(schemas.SearchWarning.additionalProperties).toBe(false); + expect(schemas.SearchWarning.required).toStrictEqual(['code', 'message']); + expect(schemas.SearchWarning.properties.cveIds.items.type).toBe('string'); + expect(schemas.SearchPagination.additionalProperties).toBe(false); + expect(schemas.SearchPagination.required).toStrictEqual([ 'page', 'pageSize', 'resultsReturned', + 'totalResults', 'totalPages', - 'status', - 'data' + 'totalIsExact' ]); - expect(searchResponse.properties.pageSize.description).toContain('requested for this page'); - expect(searchResponse.properties.resultsReturned.description).toContain('actually returned'); - expect(searchResponse.properties.totalPages.description).toContain('10,000-candidate set'); - expect(searchResponse.properties.data.properties.hits.$ref).toBe('#/components/schemas/SearchHits'); - expect(schemas.SearchHits.properties.total.properties.value.description).toContain( + expect(schemas.SearchPagination.properties.totalResults.description).toContain( 'exact number of matching records' ); - expect(schemas.SearchHits.properties.total.properties.value.description).toContain('10,000-candidate set'); - expect(schemas.SearchHits.properties.hits.items.$ref).toBe('#/components/schemas/SearchHit'); + expect(schemas.SearchPagination.properties.totalResults.description).toContain('10,000-candidate set'); + expect(schemas.SearchPagination.properties.totalIsExact.description).toContain('additional matches may exist'); + expect(schemas.CveRecord.description).toContain('without search-backend transport metadata'); expect(schemas.ApiError.required).toStrictEqual(['error', 'message']); expect(schemas.ApiErrorResponse.oneOf).toHaveLength(2); expect(openapi.paths['/search'].post.responses['200'].content['application/json'].schema.$ref).toBe( diff --git a/controllers/middleware.test.js b/controllers/middleware.test.js index 2422403..5619309 100644 --- a/controllers/middleware.test.js +++ b/controllers/middleware.test.js @@ -466,6 +466,14 @@ describe('middleware validation function', () => { expectValidationError('CVE_IDS_LIMIT', 'The cveIds parameter cannot contain more than 100 CVE IDs.'); }); + test('ValidateParams accepts syntactically valid pre-1999 CVE IDs for zero-result searches', () => { + validate({ + cveIds: 'CVE-1975-1234,CVE-1998-0001,CVE-2024-0001' + }); + + expectValidationSuccess(); + }); + test.each(Object.entries(constants.SEARCH_STRING_LENGTH_LIMITS))( 'ValidateParams returns an error when %s exceeds its maximum length.', (param, maxLength) => { diff --git a/controllers/searchController.js b/controllers/searchController.js index c31fa77..3d503dc 100644 --- a/controllers/searchController.js +++ b/controllers/searchController.js @@ -1,10 +1,12 @@ const PackageInfo = require('cve-core').packageInfo; +const constants = require('../utils/constants'); const errors = require('../utils/errors'); const searchDeadline = require('../utils/searchDeadline'); const searchProvider = require('../utils/searchProvider'); const searchQueryBuilder = require('../utils/searchQueryBuilder'); const searchPostFilters = require('../utils/searchPostFilters'); const searchPerformanceTelemetry = require('../utils/searchPerformanceTelemetry'); +const searchResponseBuilder = require('../utils/searchResponseBuilder'); const error = new errors.errors(); const openSearchUnavailableErrorNames = new Set(['ConnectionError', 'NoLivingConnectionsError']); const openSearchUnavailableStatusCodes = new Set([502, 503, 504]); @@ -16,25 +18,6 @@ function paginateHits(hits, responseOptions) { return hits.slice(from, from + resultsPerPage); } -function buildPagedSearchResponse(resp, totalValue, pagedHits) { - const hitsContainer = resp?.data?.hits; - - return { - ...resp, - data: { - ...resp.data, - hits: { - ...hitsContainer, - total: { - ...hitsContainer.total, - value: totalValue - }, - hits: pagedHits - } - } - }; -} - function getOpenSearchErrorStatusCode(err) { return Number(err?.statusCode ?? err?.meta?.statusCode ?? err?.meta?.body?.status); } @@ -109,13 +92,6 @@ function orderHitsByCveIds(hits, cveIds) { return cveIds.map((cveId) => hitsByCveId.get(cveId)).filter(Boolean); } -function combineTook(...values) { - const numericValues = values.filter((value) => Number.isFinite(value)); - if (!numericValues.length) return undefined; - - return numericValues.reduce((sum, value) => sum + value, 0); -} - async function executeOpenSearch(body, signal) { return searchProvider.searchOpenSearch(body, { signal }); } @@ -126,8 +102,10 @@ async function executeStandardSearch(searchOptions, queryStringObjects, signal, telemetry.recordValue('standardOpenSearchTookMs', searchData?.took); return { - status: 'ok', - data: searchData + totalResults: searchData.hits.total.value, + hits: searchData.hits.hits, + totalIsExact: true, + warnings: [] }; } @@ -161,9 +139,9 @@ async function buildPostFilterCandidateResult(candidateBody, postFilters, signal telemetry.recordValue('matchedCandidateCount', filteredHits.length); return { + candidateCount, filteredCount: filteredHits.length, - filteredCveIds: getHitCveIds(filteredHits), - took: candidateData?.took + filteredCveIds: getHitCveIds(filteredHits) }; } @@ -186,20 +164,14 @@ async function executePostFilterSearch( const filteredCount = candidateResult.filteredCount ?? candidateResult.filteredCveIds.length; const pageResult = await fetchFullHitsByCveIds(pageCveIds, signal, telemetry); - const response = { - status: 'ok', - data: { - took: combineTook(candidateResult.took, pageResult.took), - hits: { - total: { - value: filteredCount - }, - hits: [] - } - } - }; + const candidateLimitReached = candidateResult.candidateCount >= constants.MAX_SEARCH_WINDOW; - return buildPagedSearchResponse(response, filteredCount, pageResult.hits); + return { + totalResults: filteredCount, + hits: pageResult.hits, + totalIsExact: !candidateLimitReached, + warnings: candidateLimitReached ? [searchResponseBuilder.semanticCandidateLimitWarning()] : [] + }; } async function executeSearch(searchOptions, queryStringObjects, postFilters, responseOptions, signal, telemetry) { @@ -227,7 +199,7 @@ async function searchHandler(req, res, next) { const responseOptions = searchRequest.responseOptions; telemetry.setSearchType(postFilters); - const resp = await executeSearch( + const searchResult = await executeSearch( searchOptions, searchRequest.queryStringObjects, postFilters, @@ -237,21 +209,15 @@ async function searchHandler(req, res, next) { ); lifecycle.throwIfAborted(); const pagingOptions = responseOptions ?? searchOptions; - - //Calculate current page and total pages to append to JSON output - const totalValue = resp.data.hits.total.value; - const requestedPageSize = pagingOptions.resultsPerPage; - const resultsReturned = resp.data.hits.hits.length; - const page = totalValue === 0 ? 0 : Math.floor(pagingOptions.from / requestedPageSize) + 1; - const totalPages = totalValue === 0 ? 0 : Math.ceil(totalValue / requestedPageSize); - - return res.status(200).json({ - page, - pageSize: requestedPageSize, - resultsReturned, - totalPages, - ...resp - }); + const warnings = [...searchResponseBuilder.getCveIdWarnings(params.cveIds), ...searchResult.warnings]; + + return res.status(200).json( + searchResponseBuilder.buildSearchResponse({ + ...searchResult, + warnings, + pagingOptions + }) + ); } catch (err) { if (searchDeadline.isSearchClientDisconnectedError(err)) { telemetryOutcome = 'client_disconnected'; @@ -267,7 +233,7 @@ async function searchHandler(req, res, next) { telemetryOutcome = 'opensearch_unavailable'; if (typeof res.set === 'function') res.set('Retry-After', '5'); - return res.status(503).json(error.openSearchError()); + return res.status(503).json(error.searchUnavailableError()); } telemetryOutcome = 'upstream_error'; diff --git a/controllers/searchController.test.js b/controllers/searchController.test.js index d4b1e32..b53c3e7 100644 --- a/controllers/searchController.test.js +++ b/controllers/searchController.test.js @@ -42,6 +42,8 @@ jest.mock('../utils/searchPerformanceTelemetry', () => ({ })); const controller = require('./searchController.js'); +const constants = require('../utils/constants'); +const searchPostFilters = require('../utils/searchPostFilters'); function createResponse() { const res = {}; @@ -69,6 +71,28 @@ function rawResponse(totalValue, hits = []) { return advancedResponse(totalValue, hits).data; } +function searchSuccessResponse(totalResults, hits = [], options = {}) { + const from = options.from ?? 0; + const pageSize = options.pageSize ?? 20; + const totalIsExact = options.totalIsExact ?? true; + const warnings = options.warnings ?? []; + + return { + apiVersion: '1.0', + status: 'ok', + warnings, + pagination: { + page: totalResults === 0 ? 0 : Math.floor(from / pageSize) + 1, + pageSize, + resultsReturned: hits.length, + totalResults, + totalPages: totalResults === 0 ? 0 : Math.ceil(totalResults / pageSize), + totalIsExact + }, + records: hits.map((hit) => hit._source) + }; +} + function basicResponse({ ok = true, status = 'ok', total = 0, hits = [], notes, errors } = {}) { return { status, @@ -172,7 +196,15 @@ describe('Search controller SEARCH handler', () => { }); test('SEARCH builds filters, query strings, date ranges, and pagination options', async () => { - const resp = advancedResponse(25, [{ _id: 'CVE-2024-0001' }]); + const hit = { + _id: 'CVE-2024-0001', + _source: { + cveMetadata: { + cveId: 'CVE-2024-0001' + } + } + }; + const resp = advancedResponse(25, [hit]); mockApiSearch.mockResolvedValue(resp); await controller.SEARCH( @@ -248,13 +280,7 @@ describe('Search controller SEARCH handler', () => { ); expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - page: 3, - pageSize: 10, - resultsReturned: 1, - totalPages: 3, - ...resp - }); + expect(res.json).toHaveBeenCalledWith(searchSuccessResponse(25, [hit], { from: 20, pageSize: 10 })); }); test('SEARCH sends keywordSearch through match_phrase when keywordExactMatch is true', async () => { @@ -307,6 +333,75 @@ describe('Search controller SEARCH handler', () => { expect(mockApiSearch.mock.calls[0][0].query.bool.must).toStrictEqual([]); }); + test('SEARCH returns an empty successful response with a warning for a pre-1999 CVE ID', async () => { + mockApiSearch.mockResolvedValue(advancedResponse(0)); + + await controller.SEARCH( + { + query: { + cveIds: 'CVE-1998-0001' + } + }, + res, + next + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith( + searchSuccessResponse(0, [], { + warnings: [ + { + code: 'CVE_IDS_NOT_RETURNED', + message: + 'CVE Records do not exist for years before 1999. The listed requested IDs cannot produce results.', + cveIds: ['CVE-1998-0001'] + } + ] + }) + ); + }); + + test('SEARCH returns matching supported IDs and warns about pre-1999 IDs in the same request', async () => { + const matchingHit = { + _id: 'CVE-2024-0001', + _source: { + cveMetadata: { + cveId: 'CVE-2024-0001' + } + } + }; + mockApiSearch.mockResolvedValue(advancedResponse(1, [matchingHit])); + + await controller.SEARCH( + { + query: { + cveIds: 'cve-1998-0001,CVE-2024-0001' + } + }, + res, + next + ); + + expect(mockApiSearch.mock.calls[0][0].query.bool.filter).toContainEqual({ + terms: { + 'cveMetadata.cveId.keyword': ['CVE-1998-0001', 'CVE-2024-0001'] + } + }); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith( + searchSuccessResponse(1, [matchingHit], { + warnings: [ + { + code: 'CVE_IDS_NOT_RETURNED', + message: + 'CVE Records do not exist for years before 1999. The listed requested IDs cannot produce results.', + cveIds: ['CVE-1998-0001'] + } + ] + }) + ); + }); + test('SEARCH builds rejected-state exclusions for noRejected', async () => { mockApiSearch.mockResolvedValue(advancedResponse(2)); @@ -477,8 +572,8 @@ describe('Search controller SEARCH handler', () => { expect(res.status).toHaveBeenCalledWith(200); expect(res.json.mock.calls[0][0].status).toBe('ok'); - expect(res.json.mock.calls[0][0].data.hits.total.value).toBe(0); - expect(res.json.mock.calls[0][0].data.hits.hits).toStrictEqual([]); + expect(res.json.mock.calls[0][0].pagination.totalResults).toBe(0); + expect(res.json.mock.calls[0][0].records).toStrictEqual([]); expect(mockApiSearch).not.toHaveBeenCalled(); }); @@ -555,22 +650,7 @@ describe('Search controller SEARCH handler', () => { }) ); expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - page: 1, - pageSize: 1, - resultsReturned: 1, - totalPages: 1, - status: 'ok', - data: { - took: 22, - hits: { - total: { - value: 1 - }, - hits: [fullApplicable] - } - } - }); + expect(res.json).toHaveBeenCalledWith(searchSuccessResponse(1, [fullApplicable], { pageSize: 1 })); expect(mockSearchTelemetry.setSearchType).toHaveBeenCalledWith( expect.objectContaining({ cpeName: expect.any(Object) @@ -658,8 +738,8 @@ describe('Search controller SEARCH handler', () => { 'containers.adp.metrics.other' ]); expect(res.json.mock.calls[0][0].status).toBe('ok'); - expect(res.json.mock.calls[0][0].data.hits.total.value).toBe(1); - expect(res.json.mock.calls[0][0].data.hits.hits).toStrictEqual([fullRecord]); + expect(res.json.mock.calls[0][0].pagination.totalResults).toBe(1); + expect(res.json.mock.calls[0][0].records).toStrictEqual([fullRecord._source]); }); test('SEARCH semantically filters date-bounded KEV candidates within the selected scope', async () => { @@ -719,8 +799,8 @@ describe('Search controller SEARCH handler', () => { 'containers.adp.metrics.other' ]); expect(res.json.mock.calls[0][0].status).toBe('ok'); - expect(res.json.mock.calls[0][0].data.hits.total.value).toBe(1); - expect(res.json.mock.calls[0][0].data.hits.hits).toStrictEqual([fullRecord]); + expect(res.json.mock.calls[0][0].pagination.totalResults).toBe(1); + expect(res.json.mock.calls[0][0].records).toStrictEqual([fullRecord._source]); }); test('SEARCH applies semantic virtualMatchString version range filtering before building response metadata', async () => { @@ -783,21 +863,7 @@ describe('Search controller SEARCH handler', () => { }) ); expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - page: 1, - pageSize: 1, - resultsReturned: 1, - totalPages: 1, - status: 'ok', - data: { - hits: { - total: { - value: 1 - }, - hits: [fullApplicable] - } - } - }); + expect(res.json).toHaveBeenCalledWith(searchSuccessResponse(1, [fullApplicable], { pageSize: 1 })); }); test('SEARCH applies semantic virtualMatchString filtering before building response metadata', async () => { @@ -839,21 +905,7 @@ describe('Search controller SEARCH handler', () => { expect(mockSearchRaw.mock.calls[0][0].size).toBe(10000); expect(mockSearchRaw.mock.calls[0][0].from).toBe(0); expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - page: 1, - pageSize: 1, - resultsReturned: 1, - totalPages: 1, - status: 'ok', - data: { - hits: { - total: { - value: 1 - }, - hits: [fullApplicable] - } - } - }); + expect(res.json).toHaveBeenCalledWith(searchSuccessResponse(1, [fullApplicable], { pageSize: 1 })); }); test('SEARCH returns sanitized upstream errors when optimized post-filter search fails', async () => { @@ -879,6 +931,37 @@ describe('Search controller SEARCH handler', () => { }); }); + test('SEARCH warns when semantic results reach the bounded candidate limit', async () => { + jest.spyOn(searchPostFilters, 'filterSearchHits').mockReturnValue({ + candidateCount: constants.MAX_SEARCH_WINDOW, + filteredHits: [] + }); + mockSearchRaw.mockResolvedValue(rawResponse(constants.MAX_SEARCH_WINDOW)); + + await controller.SEARCH( + { + query: { + virtualMatchString: 'cpe:2.3:o:microsoft:windows_10_1809' + } + }, + res, + next + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith( + searchSuccessResponse(0, [], { + totalIsExact: false, + warnings: [ + { + code: 'SEMANTIC_CANDIDATE_LIMIT_REACHED', + message: 'Results and totals reflect matches found within the first 10,000 search candidates.' + } + ] + }) + ); + }); + test('SEARCH builds version-specific CVSS v3 severity filters', async () => { mockApiSearch.mockResolvedValue(advancedResponse(1)); @@ -1101,13 +1184,7 @@ describe('Search controller SEARCH handler', () => { ); expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - page: 0, - pageSize: 20, - resultsReturned: 0, - totalPages: 0, - ...resp - }); + expect(res.json).toHaveBeenCalledWith(searchSuccessResponse(0)); }); test('SEARCH calculates an out-of-range page from the requested page size', async () => { @@ -1126,13 +1203,7 @@ describe('Search controller SEARCH handler', () => { ); expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - page: 2, - pageSize: 10, - resultsReturned: 0, - totalPages: 1, - ...resp - }); + expect(res.json).toHaveBeenCalledWith(searchSuccessResponse(5, [], { from: 10, pageSize: 10 })); }); test.each([ @@ -1155,9 +1226,8 @@ describe('Search controller SEARCH handler', () => { expect(res.status).toHaveBeenCalledWith(503); expect(res.set).toHaveBeenCalledWith('Retry-After', '5'); expect(res.json).toHaveBeenCalledWith({ - error: 'OPEN_SEARCH_ERROR', - message: - 'OpenSearch refused connection, please check that OpenSearch is running and accessible then try again.' + error: 'SEARCH_UNAVAILABLE', + message: 'Search service is temporarily unavailable. Please try again later.' }); expect(mockSearchTelemetry.complete).toHaveBeenCalledWith('opensearch_unavailable'); }); diff --git a/routes/index.js b/routes/index.js index 083db99..ee990a9 100644 --- a/routes/index.js +++ b/routes/index.js @@ -198,7 +198,7 @@ router.post(

Retrieves all matching CVE records after applying the JSON request body as filters

Result Counts and Pagination

Direct searches report the exact number of matching records, including totals greater than 10,000, but requested pages must remain within the first 10,000 results.

-

CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. These searches evaluate at most 10,000 OpenSearch candidates, so their reported totals and pages include only matches found within that candidate set.

+

CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. These searches evaluate at most 10,000 search candidates, so their reported totals and pages include only matches found within that candidate set. A warning is returned when that limit is reached.

#swagger.requestBody = { required: false, content: { @@ -330,7 +330,7 @@ content: { }, } #swagger.responses[502] = { -description: 'OpenSearch returned an invalid or unexpected response', +description: 'The search backend returned an invalid or unexpected response', content: { "application/json": { schema: { @@ -340,7 +340,7 @@ content: { }, } #swagger.responses[503] = { -description: 'OpenSearch is unavailable', +description: 'The search service is temporarily unavailable', content: { "application/json": { schema: { @@ -350,7 +350,7 @@ content: { }, headers: { "Retry-After": { - description: "Seconds before retrying an unavailable OpenSearch dependency", + description: "Seconds before retrying the unavailable search service", schema: { type: "integer" } @@ -358,7 +358,7 @@ headers: { } } #swagger.responses[504] = { -description: 'Search exceeded its OpenSearch or overall processing deadline', +description: 'Search exceeded its processing deadline', content: { "application/json": { schema: { diff --git a/routes/search.integration.test.js b/routes/search.integration.test.js index 73a5219..63c3958 100644 --- a/routes/search.integration.test.js +++ b/routes/search.integration.test.js @@ -49,7 +49,22 @@ describe('search route integration', () => { }); test('POST /search validates, builds the OpenSearch request, and formats pagination metadata', async () => { - mockSearchOpenSearch.mockResolvedValue(advancedResponse(1, [{ _id: 'CVE-2024-0001' }]).data); + const record = { + dataType: 'CVE_RECORD', + cveMetadata: { + cveId: 'CVE-2024-0001' + } + }; + mockSearchOpenSearch.mockResolvedValue( + advancedResponse(1, [ + { + _index: 'private-index-name', + _id: 'CVE-2024-0001', + _score: null, + _source: record + } + ]).data + ); const response = await request(createApp(), { method: 'POST', @@ -93,10 +108,20 @@ describe('search route integration', () => { signal: expect.any(AbortSignal) } ); - expect(response.body.page).toBe(1); - expect(response.body.pageSize).toBe(1); - expect(response.body.resultsReturned).toBe(1); - expect(response.body.totalPages).toBe(1); + expect(response.body).toStrictEqual({ + apiVersion: '1.0', + status: 'ok', + warnings: [], + pagination: { + page: 1, + pageSize: 1, + resultsReturned: 1, + totalResults: 1, + totalPages: 1, + totalIsExact: true + }, + records: [record] + }); }); test('POST /search builds a scope-aware literal reference URL substring filter', async () => { diff --git a/routes/swagger.js b/routes/swagger.js index 9092d25..e8ad6ce 100644 --- a/routes/swagger.js +++ b/routes/swagger.js @@ -9,7 +9,7 @@ const projectRoot = path.join(__dirname, '..'); const outputFile = path.join(projectRoot, 'api-docs', 'openapi.json'); const routes = [path.join(__dirname, 'index.js')]; const maxSearchWindowLabel = constants.MAX_SEARCH_WINDOW.toLocaleString('en-US'); -const semanticPostFilterDescription = `CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. They evaluate at most ${maxSearchWindowLabel} OpenSearch candidates, so reported totals and pages include only matches found within that candidate set.`; +const semanticPostFilterDescription = `CVSS vector filters, SSVC filters, date-bounded positive knownExploited searches, cpeName, and virtualMatchString use semantic post-filtering. They evaluate at most ${maxSearchWindowLabel} search candidates, so reported totals and pages include only matches found within that candidate set.`; function searchRequestProperty(description, schema) { return { @@ -55,7 +55,7 @@ const searchRequestProperties = { coercibleBooleanSchema() ), cveIds: searchRequestProperty( - `Returns one or more CVE records identified by a comma-separated list of CVE IDs. Limited to ${constants.MAX_CVE_IDS} CVE IDs, ${constants.MAX_CVE_ID_LENGTH} characters per ID, and ${constants.SEARCH_STRING_LENGTH_LIMITS.cveIds} characters overall.`, + `Returns one or more CVE records identified by a comma-separated list of CVE IDs. CVE Records do not exist for years before ${constants.FIRST_CVE_RECORD_YEAR}; those IDs produce no matches and are identified by a non-fatal response warning without preventing other requested IDs from matching. Limited to ${constants.MAX_CVE_IDS} CVE IDs, ${constants.MAX_CVE_ID_LENGTH} characters per ID, and ${constants.SEARCH_STRING_LENGTH_LIMITS.cveIds} characters overall.`, { type: 'string', maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.cveIds, @@ -378,49 +378,30 @@ const doc = { } ] }, - SearchHit: { + SearchWarning: { type: 'object', - additionalProperties: true, - required: ['_source'], + additionalProperties: false, + required: ['code', 'message'], properties: { - _id: { + code: { type: 'string' }, - _source: { - type: 'object', - additionalProperties: true - } - } - }, - SearchHits: { - type: 'object', - additionalProperties: true, - required: ['total', 'hits'], - properties: { - total: { - type: 'object', - additionalProperties: true, - required: ['value'], - properties: { - value: { - type: 'integer', - minimum: 0, - description: `For direct searches, the exact number of matching records, which may be greater than ${maxSearchWindowLabel}. For semantic post-filtered searches, the number of matches found within the bounded ${maxSearchWindowLabel}-candidate set.` - } - } + message: { + type: 'string' }, - hits: { + cveIds: { type: 'array', + description: 'Requested CVE IDs associated with this warning, when applicable.', items: { - $ref: '#/components/schemas/SearchHit' + type: 'string' } } } }, - SearchResponse: { + SearchPagination: { type: 'object', additionalProperties: false, - required: ['page', 'pageSize', 'resultsReturned', 'totalPages', 'status', 'data'], + required: ['page', 'pageSize', 'resultsReturned', 'totalResults', 'totalPages', 'totalIsExact'], properties: { page: { type: 'integer', @@ -437,27 +418,55 @@ const doc = { minimum: 0, description: 'Number of CVE records actually returned in this response.' }, + totalResults: { + type: 'integer', + minimum: 0, + description: `For direct searches, the exact number of matching records, which may be greater than ${maxSearchWindowLabel}. For semantic post-filtered searches, the number of matches found within the bounded ${maxSearchWindowLabel}-candidate set.` + }, totalPages: { type: 'integer', minimum: 0, - description: `Pages derived from the reported total and requested page size. Semantic post-filtered searches derive this value from matches found within the bounded ${maxSearchWindowLabel}-candidate set.` + description: `Pages derived from totalResults and pageSize. Semantic post-filtered searches derive this value from matches found within the bounded ${maxSearchWindowLabel}-candidate set.` + }, + totalIsExact: { + type: 'boolean', + description: `True when totalResults represents every match. False when a semantic search reaches the ${maxSearchWindowLabel}-candidate limit and additional matches may exist.` + } + } + }, + CveRecord: { + type: 'object', + additionalProperties: true, + description: 'A complete CVE Record without search-backend transport metadata.' + }, + SearchResponse: { + type: 'object', + additionalProperties: false, + required: ['apiVersion', 'status', 'warnings', 'pagination', 'records'], + properties: { + apiVersion: { + type: 'string', + enum: ['1.0'], + description: 'Version of the public search response contract.' }, status: { type: 'string', enum: ['ok'] }, - data: { - type: 'object', - additionalProperties: true, - required: ['hits'], - properties: { - took: { - type: 'number', - minimum: 0 - }, - hits: { - $ref: '#/components/schemas/SearchHits' - } + warnings: { + type: 'array', + description: 'Non-fatal conditions that may affect how the results should be interpreted.', + items: { + $ref: '#/components/schemas/SearchWarning' + } + }, + pagination: { + $ref: '#/components/schemas/SearchPagination' + }, + records: { + type: 'array', + items: { + $ref: '#/components/schemas/CveRecord' } } } diff --git a/test/postman/CVE-Search-API.postman_collection.json b/test/postman/CVE-Search-API.postman_collection.json index 048dad6..e29b423 100644 --- a/test/postman/CVE-Search-API.postman_collection.json +++ b/test/postman/CVE-Search-API.postman_collection.json @@ -123,17 +123,21 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is at least 1', () => pm.expect(payload.data.hits.total.value).to.be.at.least(1));", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is at least 1', () => pm.expect(payload.pagination.totalResults).to.be.at.least(1));", "}" ] } @@ -163,17 +167,21 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is at least 1', () => pm.expect(payload.data.hits.total.value).to.be.at.least(1));", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is at least 1', () => pm.expect(payload.pagination.totalResults).to.be.at.least(1));", "}" ] } @@ -209,18 +217,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -244,6 +256,121 @@ }, "response": [] }, + { + "name": "Pre-1999 CVE ID returns no records and a warning", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('status is 200', () => pm.response.to.have.status(200));", + "pm.test('X-Request-ID is present', () => {", + " pm.expect(pm.response.headers.get('X-Request-ID')).to.be.a('string').and.not.empty;", + "});", + "const isExpectedSuccess = pm.response.code === 200;", + "if (isExpectedSuccess) {", + " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", + " const payload = pm.response.json();", + "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", + " pm.expect(payload.status).to.eql('ok');", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 0', () => pm.expect(payload.pagination.totalResults).to.eql(0));", + "pm.test('returned record count is 0', () => pm.expect(payload.records.length).to.eql(0));", + "const expectedWarnings = [{\"code\":\"CVE_IDS_NOT_RETURNED\",\"message\":\"CVE Records do not exist for years before 1999. The listed requested IDs cannot produce results.\",\"cveIds\":[\"CVE-1998-0001\"]}];", + "pm.test('expected warnings are returned', () => pm.expect(payload.warnings).to.deep.include.members(expectedWarnings));", + "}" + ] + } + } + ], + "request": { + "method": "POST", + "header": [{ "key": "Content-Type", "value": "application/json", "type": "text" }], + "url": { + "raw": "{{baseUrl}}/search", + "host": ["{{baseUrl}}"], + "path": ["search"], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"cveIds\": \"CVE-1998-0001\"\n}", + "options": { "raw": { "language": "json" } } + } + }, + "response": [] + }, + { + "name": "Mixed CVE ID years return supported matches and a warning", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('status is 200', () => pm.response.to.have.status(200));", + "pm.test('X-Request-ID is present', () => {", + " pm.expect(pm.response.headers.get('X-Request-ID')).to.be.a('string').and.not.empty;", + "});", + "const isExpectedSuccess = pm.response.code === 200;", + "if (isExpectedSuccess) {", + " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", + " const payload = pm.response.json();", + "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", + " pm.expect(payload.status).to.eql('ok');", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", + "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", + "const expectedWarnings = [{\"code\":\"CVE_IDS_NOT_RETURNED\",\"message\":\"CVE Records do not exist for years before 1999. The listed requested IDs cannot produce results.\",\"cveIds\":[\"CVE-1998-0001\"]}];", + "pm.test('expected warnings are returned', () => pm.expect(payload.warnings).to.deep.include.members(expectedWarnings));", + "}" + ] + } + } + ], + "request": { + "method": "POST", + "header": [{ "key": "Content-Type", "value": "application/json", "type": "text" }], + "url": { + "raw": "{{baseUrl}}/search", + "host": ["{{baseUrl}}"], + "path": ["search"], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"cveIds\": \"CVE-1998-0001,CVE-2024-5981\"\n}", + "options": { "raw": { "language": "json" } } + } + }, + "response": [] + }, { "name": "Multiple CVE IDs", "event": [ @@ -261,18 +388,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 2', () => pm.expect(payload.data.hits.total.value).to.eql(2));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 2', () => pm.expect(payload.pagination.totalResults).to.eql(2));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\",\"CVE-2022-31279\"]));", "}" ] @@ -313,18 +444,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - "pm.test('returned hit count is 0', () => pm.expect(payload.data.hits.hits.length).to.eql(0));", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + "pm.test('returned record count is 0', () => pm.expect(payload.records.length).to.eql(0));", "}" ] } @@ -364,18 +499,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - "pm.test('returned hit count is 0', () => pm.expect(payload.data.hits.hits.length).to.eql(0));", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + "pm.test('returned record count is 0', () => pm.expect(payload.records.length).to.eql(0));", "}" ] } @@ -421,18 +560,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -473,18 +616,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -525,18 +672,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 0', () => pm.expect(payload.data.hits.total.value).to.eql(0));", - "pm.test('returned hit count is 0', () => pm.expect(payload.data.hits.hits.length).to.eql(0));", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 0', () => pm.expect(payload.pagination.totalResults).to.eql(0));", + "pm.test('returned record count is 0', () => pm.expect(payload.records.length).to.eql(0));", "}" ] } @@ -576,18 +727,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -628,18 +783,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -680,18 +839,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2022-31640\"]));", "}" ] @@ -738,18 +901,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -790,18 +957,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -842,18 +1013,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -894,18 +1069,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -946,18 +1125,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 2', () => pm.expect(payload.data.hits.total.value).to.eql(2));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 2', () => pm.expect(payload.pagination.totalResults).to.eql(2));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\",\"CVE-2022-31279\"]));", "}" ] @@ -998,18 +1181,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1056,18 +1243,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1108,18 +1299,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1160,18 +1355,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1212,18 +1411,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1264,18 +1467,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1316,18 +1523,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1368,18 +1579,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1420,18 +1635,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1472,18 +1691,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1524,18 +1747,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1576,18 +1803,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2022-31640\"]));", "}" ] @@ -1634,18 +1865,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1686,18 +1921,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1738,18 +1977,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1790,18 +2033,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1842,18 +2089,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1894,18 +2145,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -1952,18 +2207,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2025-23022\"]));", "}" ] @@ -2004,18 +2263,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2025-23022\"]));", "}" ] @@ -2056,18 +2319,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2108,18 +2375,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2025-23022\"]));", "}" ] @@ -2160,18 +2431,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2025-23022\"]));", "}" ] @@ -2212,18 +2487,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2025-23022\"]));", "}" ] @@ -2264,18 +2543,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2025-23022\"]));", "}" ] @@ -2322,18 +2605,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2374,18 +2661,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2426,18 +2717,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2478,18 +2773,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2530,18 +2829,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2582,18 +2885,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2634,18 +2941,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2692,18 +3003,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2020-1472\"]));", "}" ] @@ -2744,18 +3059,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -2796,18 +3115,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2020-1472\"]));", "}" ] @@ -2848,18 +3171,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2020-1472\"]));", "}" ] @@ -2900,18 +3227,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2020-1472\"]));", "}" ] @@ -2958,18 +3289,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -3010,18 +3345,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -3062,18 +3401,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -3114,18 +3457,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 0', () => pm.expect(payload.data.hits.total.value).to.eql(0));", - "pm.test('returned hit count is 0', () => pm.expect(payload.data.hits.hits.length).to.eql(0));", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 0', () => pm.expect(payload.pagination.totalResults).to.eql(0));", + "pm.test('returned record count is 0', () => pm.expect(payload.records.length).to.eql(0));", "}" ] } @@ -3165,18 +3512,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2022-38625\"]));", "}" ] @@ -3217,18 +3568,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -3275,18 +3630,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2024-5981\"]));", "}" ] @@ -3327,18 +3686,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2022-31640\"]));", "}" ] @@ -3379,18 +3742,22 @@ " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", " const payload = pm.response.json();", "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - " pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);", - "});", - "pm.test('total is 1', () => pm.expect(payload.data.hits.total.value).to.eql(1));", - " const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);", + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + " pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);", + " pm.expect(payload).not.to.have.property('data');", + "});", + "pm.test('total is 1', () => pm.expect(payload.pagination.totalResults).to.eql(1));", + " const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);", "pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members([\"CVE-2020-1472\"]));", "}" ] @@ -6309,7 +6676,7 @@ "response": [] }, { - "name": "OpenSearch unavailable search", + "name": "Search service unavailable", "event": [ { "listen": "prerequest", @@ -6317,7 +6684,7 @@ "type": "text/javascript", "exec": [ "if (pm.collectionVariables.get('runFaultInjection') !== 'true') {", - " console.log('Skipping OpenSearch unavailable search; set runFaultInjection=true to enable it.');", + " console.log('Skipping Search service unavailable; set runFaultInjection=true to enable it.');", " pm.execution.skipRequest();", "}" ] @@ -6335,7 +6702,7 @@ "pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", "const payload = pm.response.json();", "const errors = Array.isArray(payload) ? payload : [payload];", - "const expectedError = {\"error\":\"OPEN_SEARCH_ERROR\",\"message\":\"OpenSearch refused connection, please check that OpenSearch is running and accessible then try again.\"};", + "const expectedError = {\"error\":\"SEARCH_UNAVAILABLE\",\"message\":\"Search service is temporarily unavailable. Please try again later.\"};", "pm.test('expected error code is returned', () => {", " pm.expect(errors.map((entry) => entry.error)).to.include(expectedError.error);", "});", @@ -6356,7 +6723,7 @@ "path": ["search"], "query": [] }, - "description": "Run only while OpenSearch connectivity is intentionally unavailable.", + "description": "Run only while search-backend connectivity is intentionally unavailable.", "body": { "mode": "raw", "raw": "{\n \"cveIds\": \"CVE-2024-5981\"\n}", diff --git a/test/postman/README.md b/test/postman/README.md index 3cc2da2..cc42259 100644 --- a/test/postman/README.md +++ b/test/postman/README.md @@ -23,7 +23,7 @@ Deployment-specific environment files are intentionally ignored by Git so deploy Folders are grouped by service checks, positive filter coverage, intentional combinations, and expected validation failures. Positive search requests assert the response contract and use stable CVE fixtures to check totals or returned IDs. Negative requests assert the exact HTTP status, error code, and error message. -The collection checks that each response includes an `X-Request-ID`. Search responses also must contain the documented status, pagination, total, and hit-array fields. +The collection checks that each response includes an `X-Request-ID`. Search responses also must contain the documented API version, status, warnings, pagination, and CVE Record array fields, without search-backend hit wrappers. ## Environments @@ -68,12 +68,12 @@ Folder `17 - Manual fault injection - skipped by default` describes runtime erro Setting `runFaultInjection` to `true` only enables the requests; it does not create their required failure conditions. Prepare and run one request at a time because the conditions are different and can conflict. -| Request | Required condition | Expected response | -| ----------------------------- | ------------------------------------------------------------------------------------------- | --------------------------- | -| OpenSearch readiness failure | Make the configured OpenSearch index unavailable to the API. | `503 SEARCH_NOT_READY` | -| OpenSearch unavailable search | Make OpenSearch connectivity, permissions, or index access unavailable. | `503 OPEN_SEARCH_ERROR` | -| Search timeout | Deliberately lower the request deadline or delay OpenSearch beyond the configured deadline. | `504 SEARCH_TIMEOUT` | -| Unexpected upstream response | Use a test double or proxy that returns an invalid or incomplete OpenSearch response. | `502 UPSTREAM_SEARCH_ERROR` | +| Request | Required condition | Expected response | +| ---------------------------- | ------------------------------------------------------------------------------------------- | --------------------------- | +| OpenSearch readiness failure | Make the configured OpenSearch index unavailable to the API. | `503 SEARCH_NOT_READY` | +| Search service unavailable | Make search-backend connectivity, permissions, or index access unavailable. | `503 SEARCH_UNAVAILABLE` | +| Search timeout | Deliberately lower the request deadline or delay OpenSearch beyond the configured deadline. | `504 SEARCH_TIMEOUT` | +| Unexpected upstream response | Use a test double or proxy that returns an invalid or incomplete OpenSearch response. | `502 UPSTREAM_SEARCH_ERROR` | Run a fault check as follows: diff --git a/test/postman/buildCollection.js b/test/postman/buildCollection.js index 412b5bb..8f40601 100644 --- a/test/postman/buildCollection.js +++ b/test/postman/buildCollection.js @@ -49,7 +49,14 @@ function requestIdTests() { ]; } -function successTests({ status = 200, exactTotal, minimumTotal, expectedIds = [], expectedHitCount } = {}) { +function successTests({ + status = 200, + exactTotal, + minimumTotal, + expectedIds = [], + expectedHitCount, + expectedWarnings = [] +} = {}) { const tests = [ `pm.test('status is ${status}', () => pm.response.to.have.status(${status}));`, ...requestIdTests(), @@ -58,41 +65,52 @@ function successTests({ status = 200, exactTotal, minimumTotal, expectedIds = [] " pm.test('response is JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));", ' const payload = pm.response.json();', "pm.test('search response has the documented shape', () => {", + " pm.expect(payload.apiVersion).to.eql('1.0');", " pm.expect(payload.status).to.eql('ok');", - " pm.expect(payload.page).to.be.a('number');", - " pm.expect(payload.pageSize).to.be.a('number');", - " pm.expect(payload.resultsReturned).to.be.a('number');", - " pm.expect(payload.totalPages).to.be.a('number');", - " pm.expect(payload.data).to.be.an('object');", - " pm.expect(payload.data.hits.total.value).to.be.a('number');", - " pm.expect(payload.data.hits.hits).to.be.an('array');", - ' pm.expect(payload.resultsReturned).to.eql(payload.data.hits.hits.length);', + " pm.expect(payload.warnings).to.be.an('array');", + " pm.expect(payload.pagination).to.be.an('object');", + " pm.expect(payload.pagination.page).to.be.a('number');", + " pm.expect(payload.pagination.pageSize).to.be.a('number');", + " pm.expect(payload.pagination.resultsReturned).to.be.a('number');", + " pm.expect(payload.pagination.totalResults).to.be.a('number');", + " pm.expect(payload.pagination.totalPages).to.be.a('number');", + " pm.expect(payload.pagination.totalIsExact).to.be.a('boolean');", + " pm.expect(payload.records).to.be.an('array');", + ' pm.expect(payload.pagination.resultsReturned).to.eql(payload.records.length);', + " pm.expect(payload).not.to.have.property('data');", '});' ]; if (exactTotal !== undefined) { tests.push( - `pm.test('total is ${exactTotal}', () => pm.expect(payload.data.hits.total.value).to.eql(${exactTotal}));` + `pm.test('total is ${exactTotal}', () => pm.expect(payload.pagination.totalResults).to.eql(${exactTotal}));` ); } else if (minimumTotal !== undefined) { tests.push( - `pm.test('total is at least ${minimumTotal}', () => pm.expect(payload.data.hits.total.value).to.be.at.least(${minimumTotal}));` + `pm.test('total is at least ${minimumTotal}', () => pm.expect(payload.pagination.totalResults).to.be.at.least(${minimumTotal}));` ); } if (expectedHitCount !== undefined) { tests.push( - `pm.test('returned hit count is ${expectedHitCount}', () => pm.expect(payload.data.hits.hits.length).to.eql(${expectedHitCount}));` + `pm.test('returned record count is ${expectedHitCount}', () => pm.expect(payload.records.length).to.eql(${expectedHitCount}));` ); } if (expectedIds.length) { tests.push( - ' const returnedIds = payload.data.hits.hits.map((hit) => hit?._source?.cveMetadata?.cveId ?? hit?._id);', + ' const returnedIds = payload.records.map((record) => record?.cveMetadata?.cveId);', `pm.test('expected CVE IDs are returned', () => pm.expect(returnedIds).to.include.members(${JSON.stringify(expectedIds)}));` ); } + if (expectedWarnings.length) { + tests.push( + `const expectedWarnings = ${JSON.stringify(expectedWarnings)};`, + "pm.test('expected warnings are returned', () => pm.expect(payload.warnings).to.deep.include.members(expectedWarnings));" + ); + } + tests.push('}'); return tests; } @@ -244,6 +262,38 @@ const baselineAndPagination = folder( positive('No request body', undefined, { minimumTotal: 1 }), positive('Default empty-object search', {}, { minimumTotal: 1 }), positive('Single CVE ID', { cveIds: ids.primary }, { exactTotal: 1, expectedIds: [ids.primary] }), + positive( + 'Pre-1999 CVE ID returns no records and a warning', + { cveIds: 'CVE-1998-0001' }, + { + exactTotal: 0, + expectedHitCount: 0, + expectedWarnings: [ + { + code: 'CVE_IDS_NOT_RETURNED', + message: + 'CVE Records do not exist for years before 1999. The listed requested IDs cannot produce results.', + cveIds: ['CVE-1998-0001'] + } + ] + } + ), + positive( + 'Mixed CVE ID years return supported matches and a warning', + { cveIds: `CVE-1998-0001,${ids.primary}` }, + { + exactTotal: 1, + expectedIds: [ids.primary], + expectedWarnings: [ + { + code: 'CVE_IDS_NOT_RETURNED', + message: + 'CVE Records do not exist for years before 1999. The listed requested IDs cannot produce results.', + cveIds: ['CVE-1998-0001'] + } + ] + } + ), positive( 'Multiple CVE IDs', { cveIds: `${ids.primary},${ids.rejected}`, resultsPerPage: 2, pageNumber: 1 }, @@ -1100,12 +1150,12 @@ const faultInjection = folder( 'GET' ), faultInjectionItem( - 'OpenSearch unavailable search', + 'Search service unavailable', '/search', { cveIds: ids.primary }, 503, - error.openSearchError(), - 'Run only while OpenSearch connectivity is intentionally unavailable.' + error.searchUnavailableError(), + 'Run only while search-backend connectivity is intentionally unavailable.' ), faultInjectionItem( 'Search timeout', diff --git a/utils/constants.js b/utils/constants.js index 3e400ef..5799eed 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -6,6 +6,7 @@ const MAX_CVSS_METRIC_TOKENS = 32; const MAX_CVSS_METRIC_TOKEN_LENGTH = 32; const MAX_CVE_IDS = 100; const MAX_CVE_ID_LENGTH = 64; +const FIRST_CVE_RECORD_YEAR = 1999; const MAX_KEYWORD_SEARCH_TOKENS = 64; const MIN_REFERENCE_URL_LENGTH = 3; const SEARCH_PERFORMANCE_TELEMETRY_INTERVAL_MS = 10000; @@ -130,6 +131,7 @@ module.exports = { MAX_CVSS_METRIC_TOKEN_LENGTH, MAX_CVE_IDS, MAX_CVE_ID_LENGTH, + FIRST_CVE_RECORD_YEAR, MAX_KEYWORD_SEARCH_TOKENS, MIN_REFERENCE_URL_LENGTH, SEARCH_PERFORMANCE_TELEMETRY_INTERVAL_MS, diff --git a/utils/errors.js b/utils/errors.js index 2b1bfa8..bd168f3 100644 --- a/utils/errors.js +++ b/utils/errors.js @@ -311,11 +311,8 @@ class errors { return makeError('WEB_SEARCH_DISABLED', 'The /webSearch endpoint is disabled by service configuration.'); } - openSearchError() { - return makeError( - 'OPEN_SEARCH_ERROR', - 'OpenSearch refused connection, please check that OpenSearch is running and accessible then try again.' - ); + searchUnavailableError() { + return makeError('SEARCH_UNAVAILABLE', 'Search service is temporarily unavailable. Please try again later.'); } searchNotReadyError() { diff --git a/utils/errors.test.js b/utils/errors.test.js index fe3e325..a253d13 100644 --- a/utils/errors.test.js +++ b/utils/errors.test.js @@ -381,12 +381,11 @@ describe('error response helpers', () => { } ], [ - 'openSearchError', + 'searchUnavailableError', [], { - error: 'OPEN_SEARCH_ERROR', - message: - 'OpenSearch refused connection, please check that OpenSearch is running and accessible then try again.' + error: 'SEARCH_UNAVAILABLE', + message: 'Search service is temporarily unavailable. Please try again later.' } ], [ diff --git a/utils/searchResponseBuilder.js b/utils/searchResponseBuilder.js new file mode 100644 index 0000000..6853413 --- /dev/null +++ b/utils/searchResponseBuilder.js @@ -0,0 +1,65 @@ +const constants = require('./constants'); + +const API_VERSION = '1.0'; + +function semanticCandidateLimitWarning() { + return { + code: 'SEMANTIC_CANDIDATE_LIMIT_REACHED', + message: `Results and totals reflect matches found within the first ${constants.MAX_SEARCH_WINDOW.toLocaleString('en-US')} search candidates.` + }; +} + +function getCveIdWarnings(value) { + if (typeof value !== 'string') return []; + + const cveIds = [ + ...new Set( + value + .split(',') + .map((cveId) => cveId.trim().toUpperCase()) + .filter((cveId) => { + const match = /^CVE-(\d{4})-\d{4,}$/.exec(cveId); + return match && Number(match[1]) < constants.FIRST_CVE_RECORD_YEAR; + }) + ) + ]; + + if (!cveIds.length) return []; + + return [ + { + code: 'CVE_IDS_NOT_RETURNED', + message: `CVE Records do not exist for years before ${constants.FIRST_CVE_RECORD_YEAR}. The listed requested IDs cannot produce results.`, + cveIds + } + ]; +} + +function buildSearchResponse({ totalResults, hits, pagingOptions, totalIsExact = true, warnings = [] }) { + const pageSize = pagingOptions.resultsPerPage; + const records = hits.map((hit) => hit._source); + const page = totalResults === 0 ? 0 : Math.floor(pagingOptions.from / pageSize) + 1; + const totalPages = totalResults === 0 ? 0 : Math.ceil(totalResults / pageSize); + + return { + apiVersion: API_VERSION, + status: 'ok', + warnings, + pagination: { + page, + pageSize, + resultsReturned: records.length, + totalResults, + totalPages, + totalIsExact + }, + records + }; +} + +module.exports = { + API_VERSION, + buildSearchResponse, + getCveIdWarnings, + semanticCandidateLimitWarning +}; diff --git a/utils/searchResponseBuilder.test.js b/utils/searchResponseBuilder.test.js new file mode 100644 index 0000000..3be94b5 --- /dev/null +++ b/utils/searchResponseBuilder.test.js @@ -0,0 +1,101 @@ +const constants = require('./constants'); +const searchResponseBuilder = require('./searchResponseBuilder'); + +describe('search response builder', () => { + test('builds the public response without OpenSearch hit metadata', () => { + const record = { + dataType: 'CVE_RECORD', + cveMetadata: { + cveId: 'CVE-2024-0001' + } + }; + const response = searchResponseBuilder.buildSearchResponse({ + totalResults: 25, + hits: [ + { + _index: 'private-index-name', + _id: 'CVE-2024-0001', + _score: 12, + _source: record, + sort: ['CVE-2024-0001'] + } + ], + pagingOptions: { + from: 20, + resultsPerPage: 10 + } + }); + + expect(Object.keys(response)).toStrictEqual(['apiVersion', 'status', 'warnings', 'pagination', 'records']); + expect(response).toStrictEqual({ + apiVersion: '1.0', + status: 'ok', + warnings: [], + pagination: { + page: 3, + pageSize: 10, + resultsReturned: 1, + totalResults: 25, + totalPages: 3, + totalIsExact: true + }, + records: [record] + }); + }); + + test('preserves warnings before records and represents an empty result page', () => { + const warnings = [searchResponseBuilder.semanticCandidateLimitWarning()]; + const response = searchResponseBuilder.buildSearchResponse({ + totalResults: 0, + hits: [], + pagingOptions: { + from: 0, + resultsPerPage: 20 + }, + totalIsExact: false, + warnings + }); + + expect(Object.keys(response).indexOf('warnings')).toBeLessThan(Object.keys(response).indexOf('records')); + expect(response.warnings).toBe(warnings); + expect(response.pagination).toStrictEqual({ + page: 0, + pageSize: 20, + resultsReturned: 0, + totalResults: 0, + totalPages: 0, + totalIsExact: false + }); + expect(response.records).toStrictEqual([]); + }); + + test('describes the bounded semantic candidate limit without naming the search implementation', () => { + const warning = searchResponseBuilder.semanticCandidateLimitWarning(); + + expect(warning).toStrictEqual({ + code: 'SEMANTIC_CANDIDATE_LIMIT_REACHED', + message: `Results and totals reflect matches found within the first ${constants.MAX_SEARCH_WINDOW.toLocaleString('en-US')} search candidates.` + }); + expect(JSON.stringify(warning)).not.toMatch(/opensearch|elastic|index|shard/i); + }); + + test('warns once for normalized, unique CVE IDs from before the first CVE Record year', () => { + expect( + searchResponseBuilder.getCveIdWarnings('cve-1998-0001,CVE-2024-0001,CVE-1975-1234,CVE-1998-0001') + ).toStrictEqual([ + { + code: 'CVE_IDS_NOT_RETURNED', + message: + 'CVE Records do not exist for years before 1999. The listed requested IDs cannot produce results.', + cveIds: ['CVE-1998-0001', 'CVE-1975-1234'] + } + ]); + }); + + test.each([undefined, '', 'CVE-1999-0001,CVE-2024-0001', 'not-a-cve'])( + 'does not warn for a request without a well-formed pre-1999 CVE ID: %p', + (value) => { + expect(searchResponseBuilder.getCveIdWarnings(value)).toStrictEqual([]); + } + ); +});