From 52bd5621f242b4791e9a8f3c496149e542bbb192 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Fri, 2 Jan 2026 10:54:42 -0500 Subject: [PATCH 01/21] Added more properties to search and highlight --- Services/dataset.service.js | 4 ++++ Services/queryGenerator.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Services/dataset.service.js b/Services/dataset.service.js index b72ac44..e21f089 100644 --- a/Services/dataset.service.js +++ b/Services/dataset.service.js @@ -36,8 +36,10 @@ const search = async (searchText, filters, options) => { 'dataset_source_repo', 'dataset_title', 'description', + 'experimental_approaches', 'dataset_source_id', 'dataset_source_url', + 'institute', 'PI_name', // 'GPA', 'dataset_doc', @@ -100,8 +102,10 @@ const export2CSV = async (searchText, filters, options) => { 'dataset_source_repo', 'dataset_title', 'description', + 'experimental_approaches', 'dataset_source_id', 'dataset_source_url', + 'institute', 'PI_name', // 'GPA', 'dataset_doc', diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index 5cf3f53..631df8b 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -7,9 +7,11 @@ const DATASET_FIELDS = [ 'description', // 'dataset_maximum_age_at_baseline', // 'dataset_minimum_age_at_baseline', + 'experimental_approaches', 'dataset_source_id', 'dataset_source_repo', 'dataset_source_url', + 'institute', // 'dataset_year_enrollment_ended', // 'dataset_year_enrollment_started', 'PI_name', @@ -248,9 +250,11 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = 'description.search': { number_of_fragments: 0 }, 'dataset_maximum_age_at_baseline.search': { number_of_fragments: 0 }, 'dataset_minimum_age_at_baseline.search': { number_of_fragments: 0 }, + 'experimental_approaches.search': { number_of_fragments: 0 }, 'dataset_source_id.search': { number_of_fragments: 0 }, 'dataset_source_repo.search': { number_of_fragments: 0 }, 'dataset_source_url.search': { number_of_fragments: 0 }, + 'institute.search': { number_of_fragments: 0 }, 'dataset_year_enrollment_ended.search': { number_of_fragments: 0 }, 'dataset_year_enrollment_started.search': { number_of_fragments: 0 }, 'PI_name.search': { number_of_fragments: 0 }, From 1ed27c7bcc2b3704989824e26d241299de72eeda Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 5 Jan 2026 11:39:52 -0500 Subject: [PATCH 02/21] Added CSV header mapping for new properties --- Utils/datasetFields.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Utils/datasetFields.js b/Utils/datasetFields.js index 080b928..488c377 100644 --- a/Utils/datasetFields.js +++ b/Utils/datasetFields.js @@ -3,9 +3,11 @@ const datasetFields = { 'Dataset UUID': 'dataset_uuid', 'Dataset Title': 'dataset_title', 'Description': 'description', + 'Experimental Approaches': 'experimental_approaches', 'Dataset Source ID': 'dataset_source_id', 'Dataset Source Repository': 'dataset_source_repo', 'Dataset Source URL': 'dataset_source_url', + 'Institute': 'institute', 'Principal Investigator(s)': 'PI_name', // Specifically exclude GPA, because we don't display it anywhere // 'Grant Program Administrator': 'GPA', From 62def55e425af5b7fa3db891c3bdb0238a072b13 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 5 Jan 2026 14:33:37 -0500 Subject: [PATCH 03/21] Removed highlighting for numerical and date properties --- Services/queryGenerator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index 631df8b..85b1dcc 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -248,15 +248,15 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = // 'dataset_uuid': { number_of_fragments: 0 }, 'dataset_title.search': { number_of_fragments: 0 }, 'description.search': { number_of_fragments: 0 }, - 'dataset_maximum_age_at_baseline.search': { number_of_fragments: 0 }, - 'dataset_minimum_age_at_baseline.search': { number_of_fragments: 0 }, + // 'dataset_maximum_age_at_baseline.search': { number_of_fragments: 0 }, + // 'dataset_minimum_age_at_baseline.search': { number_of_fragments: 0 }, 'experimental_approaches.search': { number_of_fragments: 0 }, 'dataset_source_id.search': { number_of_fragments: 0 }, 'dataset_source_repo.search': { number_of_fragments: 0 }, 'dataset_source_url.search': { number_of_fragments: 0 }, 'institute.search': { number_of_fragments: 0 }, - 'dataset_year_enrollment_ended.search': { number_of_fragments: 0 }, - 'dataset_year_enrollment_started.search': { number_of_fragments: 0 }, + // 'dataset_year_enrollment_ended.search': { number_of_fragments: 0 }, + // 'dataset_year_enrollment_started.search': { number_of_fragments: 0 }, 'PI_name.search': { number_of_fragments: 0 }, // 'GPA': { number_of_fragments: 0 }, 'dataset_doc.search': { number_of_fragments: 0 }, From e2419985b80a38dbc586349f237abdc12b8ea75d Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 5 Jan 2026 20:41:27 +0000 Subject: [PATCH 04/21] Added rollup module to support vitest --- package-lock.json | 23 ++++++++++++++++++----- package.json | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08ba19b..32830c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@elastic/elasticsearch": "7.10.0", "@opensearch-project/opensearch": "^1.0.0", + "@rollup/rollup-linux-arm64-gnu": "^4.55.1", "body-parser": "^1.20.3", "compression": "^1.7.4", "cookie-parser": "^1.4.5", @@ -722,15 +723,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", - "optional": true, "os": [ "linux" ] @@ -2759,6 +2758,20 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", diff --git a/package.json b/package.json index 9f77038..7732fe7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@elastic/elasticsearch": "7.10.0", "@opensearch-project/opensearch": "^1.0.0", + "@rollup/rollup-linux-arm64-gnu": "^4.55.1", "body-parser": "^1.20.3", "compression": "^1.7.4", "cookie-parser": "^1.4.5", From 11e6c703541311a8244ef5a819e0aca1ed7315d0 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 5 Jan 2026 23:01:14 +0000 Subject: [PATCH 05/21] Filter, search, and highlight properties now imported from constants; starting queryGenerator test coverage --- Services/dataset.service.js | 65 +------------ Services/queryGenerator.js | 101 +++++++-------------- Services/queryGenerator.test.js | 156 ++++++++++++++++++++++++++++++++ Utils/datasetFields.js | 68 ++++++++++++++ 4 files changed, 260 insertions(+), 130 deletions(-) create mode 100644 Services/queryGenerator.test.js diff --git a/Services/dataset.service.js b/Services/dataset.service.js index e21f089..d70c2f9 100644 --- a/Services/dataset.service.js +++ b/Services/dataset.service.js @@ -6,6 +6,7 @@ const queryGenerator = require('./queryGenerator'); const cacheKeyGenerator = require('./cacheKeyGenerator'); const utils = require('../Utils'); +const { DATASET_RETURN_FIELDS } = require('../Utils/datasetFields.js'); const FACET_FILTERS = [ 'dataset_source_repo', 'primary_disease', @@ -31,37 +32,7 @@ const search = async (searchText, filters, options) => { result.aggs = 'all'; } - const returnFields = [ - // 'dataset_uuid', - 'dataset_source_repo', - 'dataset_title', - 'description', - 'experimental_approaches', - 'dataset_source_id', - 'dataset_source_url', - 'institute', - 'PI_name', - // 'GPA', - 'dataset_doc', - 'dataset_pmid', - 'funding_source', - 'release_date', - 'limitations_for_reuse', - 'assay_method', - 'study_type', - 'primary_disease', - 'participant_count', - 'sample_count', - 'study_links', - 'related_genes', - 'related_diseases', - 'related_terms', - 'dataset_year_enrollment_started', - 'dataset_year_enrollment_ended', - 'dataset_minimum_age_at_baseline', - 'dataset_maximum_age_at_baseline' - ]; - let query = queryGenerator.getSearchQueryV2(searchText, filters, options, returnFields); + let query = queryGenerator.getSearchQueryV2(searchText, filters, options, DATASET_RETURN_FIELDS); let searchResults = await elasticsearch.searchWithAggregations(config.indexDS, query); let datasets = searchResults.hits.hits.map((ds) => { if (ds.inner_hits) { @@ -97,37 +68,7 @@ const search = async (searchText, filters, options) => { }; const export2CSV = async (searchText, filters, options) => { - const returnFields = [ - 'dataset_uuid', - 'dataset_source_repo', - 'dataset_title', - 'description', - 'experimental_approaches', - 'dataset_source_id', - 'dataset_source_url', - 'institute', - 'PI_name', - // 'GPA', - 'dataset_doc', - 'dataset_pmid', - 'funding_source', - 'release_date', - 'limitations_for_reuse', - 'assay_method', - 'study_type', - 'primary_disease', - 'participant_count', - 'sample_count', - 'study_links', - 'related_genes', - 'related_diseases', - 'related_terms', - 'dataset_year_enrollment_started', - 'dataset_year_enrollment_ended', - 'dataset_minimum_age_at_baseline', - 'dataset_maximum_age_at_baseline' - ]; - const query = queryGenerator.getSearchQueryV2(searchText, filters, options, returnFields); + const query = queryGenerator.getSearchQueryV2(searchText, filters, options, DATASET_RETURN_FIELDS); const searchResults = await elasticsearch.search(config.indexDS, query); const datasets = searchResults.hits.map((dataset) => dataset._source); diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index 85b1dcc..f57c9eb 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -1,36 +1,7 @@ const { query } = require("winston"); const config = require("../Config"); const { values } = require("lodash"); -const DATASET_FIELDS = [ - // 'dataset_uuid', - 'dataset_title', - 'description', - // 'dataset_maximum_age_at_baseline', - // 'dataset_minimum_age_at_baseline', - 'experimental_approaches', - 'dataset_source_id', - 'dataset_source_repo', - 'dataset_source_url', - 'institute', - // 'dataset_year_enrollment_ended', - // 'dataset_year_enrollment_started', - 'PI_name', - // 'GPA', - 'dataset_doc', - 'dataset_pmid', - 'funding_source', - // 'release_date', - 'limitations_for_reuse', - 'assay_method', - 'study_type', - 'primary_disease', - // 'participant_count', - // 'sample_count', - 'study_links', - 'related_genes', - 'related_diseases', - 'related_terms', -]; +const { DATASET_SEARCH_FIELDS, DATASET_HIGHLIGHT_FIELDS } = require('../Utils/datasetFields.js'); let queryGenerator = {}; @@ -152,6 +123,11 @@ queryGenerator.getSearchAggregationQuery = (searchText) => { }; queryGenerator.getFiltersClause = (filters) => { + // Handle null parameter + if (!filters) { + return null; + } + // Ignore filters with no values selected const cleanedFilters = Object.fromEntries( Object.entries(filters).filter(([field, values]) => values.length > 0) @@ -171,7 +147,25 @@ queryGenerator.getFiltersClause = (filters) => { return clause; } +queryGenerator.getHighlightClause = () => { + const fieldsMap = DATASET_HIGHLIGHT_FIELDS.reduce((acc, field) => { + acc[field] = { number_of_fragments: 0 }; + return acc; + }, {}); + + return { + pre_tags: [""], + post_tags: [""], + fields: fieldsMap, + }; +}; + queryGenerator.getTextSearchConditions = (searchText) => { + // Handle null parameter + if (!searchText) { + return []; + } + const conditions = []; const searchTerms = searchText.trim().split(' ').map( term => term.trim() @@ -187,7 +181,7 @@ queryGenerator.getTextSearchConditions = (searchText) => { const dsl = { 'multi_match': { 'query': term, - 'fields': DATASET_FIELDS.map((field) => `${field}.search`), + 'fields': DATASET_SEARCH_FIELDS, } }; @@ -207,11 +201,14 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = const filtersClause = queryGenerator.getFiltersClause(filters); const textSearchClause = queryGenerator.getTextSearchConditions(searchText); - body['_source'] = returnFields; + body['_source'] = returnFields && returnFields.length > 0 ? returnFields : false; - if (options) { + if (options?.pageInfo?.pageSize) { body.size = options.pageInfo.pageSize; - body.from = (options.pageInfo.page - 1 ) * options.pageInfo.pageSize; + + if (options.pageInfo.page) { + body.from = body.size * (options.pageInfo.page - 1); + } } if (filtersClause != null) { @@ -241,40 +238,8 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = body.sort.push(sortClause); } - body.highlight = { - pre_tags: [""], - post_tags: [""], - fields: { - // 'dataset_uuid': { number_of_fragments: 0 }, - 'dataset_title.search': { number_of_fragments: 0 }, - 'description.search': { number_of_fragments: 0 }, - // 'dataset_maximum_age_at_baseline.search': { number_of_fragments: 0 }, - // 'dataset_minimum_age_at_baseline.search': { number_of_fragments: 0 }, - 'experimental_approaches.search': { number_of_fragments: 0 }, - 'dataset_source_id.search': { number_of_fragments: 0 }, - 'dataset_source_repo.search': { number_of_fragments: 0 }, - 'dataset_source_url.search': { number_of_fragments: 0 }, - 'institute.search': { number_of_fragments: 0 }, - // 'dataset_year_enrollment_ended.search': { number_of_fragments: 0 }, - // 'dataset_year_enrollment_started.search': { number_of_fragments: 0 }, - 'PI_name.search': { number_of_fragments: 0 }, - // 'GPA': { number_of_fragments: 0 }, - 'dataset_doc.search': { number_of_fragments: 0 }, - 'dataset_pmid.search': { number_of_fragments: 0 }, - 'funding_source.search': { number_of_fragments: 0 }, - // 'release_date': { number_of_fragments: 0 }, - 'limitations_for_reuse.search': { number_of_fragments: 0 }, - 'assay_method.search': { number_of_fragments: 0 }, - 'study_type.search': { number_of_fragments: 0 }, - 'primary_disease.search': { number_of_fragments: 0 }, - // 'participant_count': { number_of_fragments: 0 }, - // 'sample_count': { number_of_fragments: 0 }, - 'study_links.search': { number_of_fragments: 0 }, - 'related_genes.search': { number_of_fragments: 0 }, - 'related_diseases.search': { number_of_fragments: 0 }, - 'related_terms.search': { number_of_fragments: 0 }, - }, - }; + body.highlight = queryGenerator.getHighlightClause(); + return body; }; diff --git a/Services/queryGenerator.test.js b/Services/queryGenerator.test.js new file mode 100644 index 0000000..0a4a76b --- /dev/null +++ b/Services/queryGenerator.test.js @@ -0,0 +1,156 @@ +import { describe, it, expect } from 'vitest'; +const queryGenerator = require('./queryGenerator.js'); +const { DATASET_SEARCH_FIELDS } = require('../Utils/datasetFields.js'); + +const oSHighlightClause = queryGenerator.getHighlightClause(); +const normalOSQuery = { + _source: ['dataset_title', 'description'], + size: 10, + from: 0, + query: { + bool: { + must: [ + { + multi_match: { + query: "multiple", + fields: DATASET_SEARCH_FIELDS, + }, + }, + { + multi_match: { + query: "myeloma", + fields: DATASET_SEARCH_FIELDS, + }, + }, + ], + filter: [ + { + terms: { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types", + ], + }, + }, + { + terms: { + dataset_source_repo: [ + "CEDCD", + "dbGaP", + ], + }, + }, + ], + }, + }, + sort: [ + { + dataset_title_sort: "asc", + }, + ], + highlight: oSHighlightClause, +}; +const nullOSQuery = { + _source: false, + highlight: oSHighlightClause, +}; + +describe('getSearchQueryV2', () => { + it('should form a correct query when all parameters are provided', () => { + const searchText = 'multiple myeloma'; + const filters = { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types", + ], + dataset_source_repo: [ + "CEDCD", + "dbGaP", + ], + }; + const options = { + pageInfo: { + page: 1, + pageSize: 10, + }, + sort: { + name: "Dataset", + k: "dataset_title_sort", + v: "asc", + }, + }; + const returnFields = ['dataset_title', 'description']; + const result = queryGenerator.getSearchQueryV2(searchText, filters, options, returnFields); + expect(result).toStrictEqual(normalOSQuery); + }); + + it('should correctly handle null parameters', () => { + const result = queryGenerator.getSearchQueryV2(null, null, null, null); + expect(result).toStrictEqual(nullOSQuery); + }); + + it('should correctly handle empty parameters', () => { + const result = queryGenerator.getSearchQueryV2("", {}, {}, []); + expect(result).toStrictEqual(nullOSQuery); + }); + + // it('should handle leading and trailing spaces', () => { + // const result = utils.getSearchableText(' hello world '); + // expect(result).toBe('hello world'); + // }); + + // it('should return empty string when all terms are 2 or fewer characters', () => { + // const result = utils.getSearchableText('a to in at'); + // expect(result).toBe(''); + // }); + + // it('should return empty string for empty input', () => { + // const result = utils.getSearchableText(''); + // expect(result).toBe(''); + // }); + + // it('should return empty string for whitespace only input', () => { + // const result = utils.getSearchableText(' '); + // expect(result).toBe(''); + // }); + + // it('should handle single word longer than 2 characters', () => { + // const result = utils.getSearchableText('test'); + // expect(result).toBe('test'); + // }); + + // it('should filter out single word with 2 or fewer characters', () => { + // const result = utils.getSearchableText('at'); + // expect(result).toBe(''); + // }); + + // it('should handle mixed case text', () => { + // const result = utils.getSearchableText('Hello WORLD Test'); + // expect(result).toBe('Hello WORLD Test'); + // }); + + // it('should preserve special characters in terms longer than 2 characters', () => { + // const result = utils.getSearchableText('test-123 data@2024'); + // expect(result).toBe('test-123 data@2024'); + // }); + + // it('should handle terms with exactly 3 characters', () => { + // const result = utils.getSearchableText('the cat dog'); + // expect(result).toBe('the cat dog'); + // }); + + // it('should filter terms with exactly 2 characters', () => { + // const result = utils.getSearchableText('in on at to my cat'); + // expect(result).toBe('cat'); + // }); + + // it('should filter terms with exactly 1 character', () => { + // const result = utils.getSearchableText('a b c test hello'); + // expect(result).toBe('test hello'); + // }); + + // it('should handle complex mixed input', () => { + // const result = utils.getSearchableText(' a hello to world in test '); + // expect(result).toBe('hello world test'); + // }); +}); diff --git a/Utils/datasetFields.js b/Utils/datasetFields.js index 488c377..4596224 100644 --- a/Utils/datasetFields.js +++ b/Utils/datasetFields.js @@ -1,4 +1,69 @@ // Maps Dataset natural field names to property names +const DATASET_SEARCH_FIELDS = [ + // 'dataset_uuid', + 'dataset_source_repo.search', + 'dataset_title.search', + 'description.search', + 'experimental_approaches.search', + 'dataset_source_id.search', + 'dataset_source_url.search', + 'institute.search', + 'PI_name.search', + // 'GPA', + 'dataset_doc.search', + // POC_name, + // POC_email, + // 'dataset_maximum_age_at_baseline', + // 'dataset_minimum_age_at_baseline', + 'dataset_pmid.search', + // 'dataset_year_enrollment_ended', + // 'dataset_year_enrollment_started', + 'funding_source.search', + // 'release_date', + 'limitations_for_reuse.search', + 'assay_method.search', + 'study_type.search', + 'primary_disease.search', + // 'participant_count', + // 'sample_count', + 'study_links.search', + 'related_genes.search', + 'related_diseases.search', + 'related_terms.search', +]; +const DATASET_HIGHLIGHT_FIELDS = DATASET_SEARCH_FIELDS; +const DATASET_RETURN_FIELDS = [ + // 'dataset_uuid', + 'dataset_source_repo', + 'dataset_title', + 'description', + 'experimental_approaches', + 'dataset_source_id', + 'dataset_source_url', + 'institute', + 'PI_name', + // 'GPA', + 'dataset_doc', + // POC_name, + // POC_email, + 'dataset_maximum_age_at_baseline', + 'dataset_minimum_age_at_baseline', + 'dataset_pmid', + 'dataset_year_enrollment_ended', + 'dataset_year_enrollment_started', + 'funding_source', + 'release_date', + 'limitations_for_reuse', + 'assay_method', + 'study_type', + 'primary_disease', + 'participant_count', + 'sample_count', + 'study_links', + 'related_genes', + 'related_diseases', + 'related_terms', +]; const datasetFields = { 'Dataset UUID': 'dataset_uuid', 'Dataset Title': 'dataset_title', @@ -28,6 +93,9 @@ const datasetFields = { }; module.exports = { + DATASET_SEARCH_FIELDS, + DATASET_HIGHLIGHT_FIELDS, + DATASET_RETURN_FIELDS, datasetFields, }; From 4a0e530ab392c0b1eacab48de541906e37a502d3 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Tue, 6 Jan 2026 02:56:49 +0000 Subject: [PATCH 06/21] Revert "Added rollup module to support vitest" This reverts commit e2419985b80a38dbc586349f237abdc12b8ea75d. --- package-lock.json | 23 +++++------------------ package.json | 1 - 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 32830c6..08ba19b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@elastic/elasticsearch": "7.10.0", "@opensearch-project/opensearch": "^1.0.0", - "@rollup/rollup-linux-arm64-gnu": "^4.55.1", "body-parser": "^1.20.3", "compression": "^1.7.4", "cookie-parser": "^1.4.5", @@ -723,13 +722,15 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", - "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", + "optional": true, "os": [ "linux" ] @@ -2758,20 +2759,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", diff --git a/package.json b/package.json index 7732fe7..9f77038 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "dependencies": { "@elastic/elasticsearch": "7.10.0", "@opensearch-project/opensearch": "^1.0.0", - "@rollup/rollup-linux-arm64-gnu": "^4.55.1", "body-parser": "^1.20.3", "compression": "^1.7.4", "cookie-parser": "^1.4.5", From 9e5cced0fd955b7327c7a63b9b8f3366acb4532a Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Tue, 6 Jan 2026 02:57:53 +0000 Subject: [PATCH 07/21] Added rollup as a dev dependency --- package-lock.json | 228 ++++++++++++++++++++++++++++------------------ package.json | 1 + 2 files changed, 138 insertions(+), 91 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08ba19b..d614837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ }, "devDependencies": { "@vitest/coverage-v8": "^4.0.15", + "rollup": "^4.55.1", "vitest": "^4.0.15" } }, @@ -610,9 +611,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", "cpu": [ "arm" ], @@ -624,9 +625,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", "cpu": [ "arm64" ], @@ -638,9 +639,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", "cpu": [ "arm64" ], @@ -652,9 +653,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", "cpu": [ "x64" ], @@ -666,9 +667,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", "cpu": [ "arm64" ], @@ -680,9 +681,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", "cpu": [ "x64" ], @@ -694,9 +695,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", "cpu": [ "arm" ], @@ -708,9 +709,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", "cpu": [ "arm" ], @@ -722,9 +723,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", "cpu": [ "arm64" ], @@ -736,9 +737,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", "cpu": [ "arm64" ], @@ -750,9 +751,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", "cpu": [ "loong64" ], @@ -764,9 +779,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", "cpu": [ "ppc64" ], @@ -778,9 +807,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", "cpu": [ "riscv64" ], @@ -792,9 +821,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", "cpu": [ "riscv64" ], @@ -806,9 +835,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", "cpu": [ "s390x" ], @@ -820,9 +849,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", "cpu": [ "x64" ], @@ -834,9 +863,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", "cpu": [ "x64" ], @@ -847,10 +876,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", "cpu": [ "arm64" ], @@ -862,9 +905,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", "cpu": [ "arm64" ], @@ -876,9 +919,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", "cpu": [ "ia32" ], @@ -890,9 +933,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", "cpu": [ "x64" ], @@ -904,9 +947,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", "cpu": [ "x64" ], @@ -2718,9 +2761,9 @@ } }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "dev": true, "license": "MIT", "dependencies": { @@ -2734,28 +2777,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" } }, diff --git a/package.json b/package.json index 9f77038..f3b2fd6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "devDependencies": { "@vitest/coverage-v8": "^4.0.15", + "rollup": "^4.55.1", "vitest": "^4.0.15" } } From 6858b5028d91a45dce435879ea7b3772352458b2 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Tue, 13 Jan 2026 19:32:56 +0000 Subject: [PATCH 08/21] Wrote some tests for the dataset query generator --- Services/queryGenerator.js | 61 ++- Services/queryGenerator.test.fixtures.js | 84 ++++ Services/queryGenerator.test.js | 466 ++++++++++++++++------- 3 files changed, 466 insertions(+), 145 deletions(-) create mode 100644 Services/queryGenerator.test.fixtures.js diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index f57c9eb..2db0e74 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -123,8 +123,12 @@ queryGenerator.getSearchAggregationQuery = (searchText) => { }; queryGenerator.getFiltersClause = (filters) => { - // Handle null parameter - if (!filters) { + // Handle null or invalid parameter + if ( + !filters || + typeof filters !== 'object' || + Array.isArray(filters) + ) { return null; } @@ -160,9 +164,36 @@ queryGenerator.getHighlightClause = () => { }; }; +queryGenerator.getSortClause = (options) => { + // Handle null or wrong type + if (!options || typeof options !== 'object' || Array.isArray(options)) { + return null; + } + + // Check whether a sort object exists + if (!options.sort || typeof options.sort !== 'object' || Array.isArray(options.sort)) { + return null; + } + + // Check whether the sort object has a 'k' and 'v' property + if (!options.sort.k || !options.sort.v) { + return null; + } + + // Check whether the sort property and direction are strings + if (typeof options.sort.k !== 'string' || typeof options.sort.v !== 'string') { + return null; + } + + // Return the sort clause + return { + [options.sort.k]: options.sort.v, + }; +}; + queryGenerator.getTextSearchConditions = (searchText) => { // Handle null parameter - if (!searchText) { + if (!searchText || typeof searchText !== 'string') { return []; } @@ -203,14 +234,23 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = body['_source'] = returnFields && returnFields.length > 0 ? returnFields : false; - if (options?.pageInfo?.pageSize) { - body.size = options.pageInfo.pageSize; + if (options && typeof options === 'object' && !Array.isArray(options)) { + if (options.pageInfo?.pageSize > 0) { + body.size = options.pageInfo.pageSize; - if (options.pageInfo.page) { - body.from = body.size * (options.pageInfo.page - 1); + if (options.pageInfo.page > 0) { + body.from = body.size * (options.pageInfo.page - 1); + } else { + body.from = 0; + } } } + const sortClause = queryGenerator.getSortClause(options); + if (sortClause != null) { + body.sort = [sortClause]; + } + if (filtersClause != null) { compoundQuery.bool['filter'] = filtersClause; } @@ -230,13 +270,6 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = agg.myAgg.terms.size = 1000; // body.aggs = agg; - // Add sort parameters - if (options?.sort) { - body.sort = []; // Initialize a list of sort clauses - const sortClause = {}; - sortClause[options.sort.k] = options.sort.v; // In our API, "k" is the property name, and "v" is the direction - body.sort.push(sortClause); - } body.highlight = queryGenerator.getHighlightClause(); diff --git a/Services/queryGenerator.test.fixtures.js b/Services/queryGenerator.test.fixtures.js new file mode 100644 index 0000000..c2e10c8 --- /dev/null +++ b/Services/queryGenerator.test.fixtures.js @@ -0,0 +1,84 @@ +/** + * Fixtures for the queryGenerator.test.js file + */ +const { DATASET_SEARCH_FIELDS } = require('../Utils/datasetFields.js'); +const queryGenerator = require('./queryGenerator.js'); + +// Input parameters +export const normalSearchText = 'multiple myeloma'; +export const normalFilters = { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types", + ], + dataset_source_repo: [ + "CEDCD", + "dbGaP", + ], +}; +export const normalReturnFields = ['dataset_title', 'description']; +export const normalOptions = { + pageInfo: { + page: 1, + pageSize: 10, + }, + sort: { + name: "Dataset", + k: "dataset_title_sort", + v: "asc", + }, +}; + +// Reference queries +export const oSHighlightClause = queryGenerator.getHighlightClause(); +export const normalOSQuery = { + _source: ['dataset_title', 'description'], + size: 10, + from: 0, + query: { + bool: { + must: [ + { + multi_match: { + query: "multiple", + fields: DATASET_SEARCH_FIELDS, + }, + }, + { + multi_match: { + query: "myeloma", + fields: DATASET_SEARCH_FIELDS, + }, + }, + ], + filter: [ + { + terms: { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types", + ], + }, + }, + { + terms: { + dataset_source_repo: [ + "CEDCD", + "dbGaP", + ], + }, + }, + ], + }, + }, + sort: [ + { + dataset_title_sort: "asc", + }, + ], + highlight: oSHighlightClause, +}; +export const nullOSQuery = { + _source: false, + highlight: oSHighlightClause, +}; diff --git a/Services/queryGenerator.test.js b/Services/queryGenerator.test.js index 0a4a76b..3bd0a7b 100644 --- a/Services/queryGenerator.test.js +++ b/Services/queryGenerator.test.js @@ -1,156 +1,360 @@ import { describe, it, expect } from 'vitest'; const queryGenerator = require('./queryGenerator.js'); const { DATASET_SEARCH_FIELDS } = require('../Utils/datasetFields.js'); +import { normalSearchText, normalFilters, normalReturnFields, normalOptions } from './queryGenerator.test.fixtures.js'; -const oSHighlightClause = queryGenerator.getHighlightClause(); -const normalOSQuery = { - _source: ['dataset_title', 'description'], - size: 10, - from: 0, - query: { - bool: { - must: [ - { - multi_match: { - query: "multiple", - fields: DATASET_SEARCH_FIELDS, - }, - }, - { - multi_match: { - query: "myeloma", - fields: DATASET_SEARCH_FIELDS, - }, +// Opensearch +import { normalOSQuery, nullOSQuery, oSHighlightClause } from './queryGenerator.test.fixtures.js'; + +describe('getSearchQueryV2', () => { + it('should handle undefined parameters', () => { // 1 + const result = queryGenerator.getSearchQueryV2(undefined, undefined, undefined, undefined); + expect(result).toStrictEqual(nullOSQuery); + }); + + it('should handle null parameters', () => { // 2 + const result = queryGenerator.getSearchQueryV2(null, null, null, null); + expect(result).toStrictEqual(nullOSQuery); + }); + + it('should correctly handle empty parameters', () => { // 3 + const result = queryGenerator.getSearchQueryV2('', {}, {}, []); + expect(result).toStrictEqual(nullOSQuery); + }); + + it('should handle searchText being the wrong type', () => { // 4 + // Test with a number + const resultNumber = queryGenerator.getSearchQueryV2(12345, null, null, null); + expect(resultNumber).toStrictEqual(nullOSQuery); + }); + + it('should handle whitespace-only searchText', () => { // 5 + const result = queryGenerator.getSearchQueryV2(' ', null, null, null); + expect(result).toStrictEqual(nullOSQuery); + }); + + it('should form a correct query when all parameters are provided', () => { // 6 + const result = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, normalOptions, normalReturnFields); + expect(result).toStrictEqual(normalOSQuery); + }); + + it('should handle filters being the wrong type', () => { // 7 + // Pass filters as a string + const resultString = queryGenerator.getSearchQueryV2(null, 'not an object', null, null); + expect(resultString).toStrictEqual(nullOSQuery); + + // Pass filters as an array + const resultArray = queryGenerator.getSearchQueryV2(null, ['a', 'b'], null, null); + expect(resultArray).toStrictEqual(nullOSQuery); + }); + + it('should handle options being the wrong type', () => { // 8 + // Pass options as a string + const resultString = queryGenerator.getSearchQueryV2(null, null, "not an object", null); + expect(resultString).toStrictEqual(nullOSQuery); + + // Pass options as an array + const resultArray = queryGenerator.getSearchQueryV2(null, null, [1,2,3], null); + expect(resultArray).toStrictEqual(nullOSQuery); + }); + + it('should ignore page options being the wrong type', () => { // 9 + const optionsWithZeroPage = { + ...normalOptions, + pageInfo: { + ...normalOptions.pageInfo, + page: 0, + }, + }; + const result = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, optionsWithZeroPage, normalReturnFields); + expect(result).toStrictEqual(normalOSQuery); + }); + + it('should handle search only (no filters/options/fields)', () => { + const searchText = 'multiple myeloma'; + const expectedQuery = { + _source: false, + query: { + bool: { + must: [ + { + multi_match: { + query: 'multiple', + fields: DATASET_SEARCH_FIELDS, + }, + }, + { + multi_match: { + query: 'myeloma', + fields: DATASET_SEARCH_FIELDS, + }, + }, + ], }, - ], - filter: [ - { - terms: { - primary_disease: [ - "Melanoma", - "Multiple Cancer Types", - ], - }, + }, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(searchText, null, null, []); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle filters only (no search/options/fields)', () => { + const expectedQuery = { + _source: false, + query: { + bool: { + filter: [ + { + terms: { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types", + ], + }, + }, + { + terms: { + dataset_source_repo: [ + "CEDCD", + "dbGaP", + ], + }, + }, + ], + must: [], }, + }, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(null, normalFilters, null, []); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle returnFields only (all else empty)', () => { + const expectedQuery = { + _source: ['dataset_title', 'description'], + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(null, null, null, normalReturnFields); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle options with pagination only', () => { + const options = { + pageInfo: { + page: 3, + pageSize: 25, + }, + }; + const expectedQuery = { + _source: false, + size: 25, + from: 50, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(null, null, options, []); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle options with sorting only', () => { + const options = { + sort: { + k: 'dataset_title_sort', + v: 'desc', + }, + }; + const expectedQuery = { + _source: false, + sort: [ { - terms: { - dataset_source_repo: [ - "CEDCD", - "dbGaP", - ], - }, + dataset_title_sort: 'desc', }, ], - }, - }, - sort: [ - { - dataset_title_sort: "asc", - }, - ], - highlight: oSHighlightClause, -}; -const nullOSQuery = { - _source: false, - highlight: oSHighlightClause, -}; + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(null, null, options, []); + expect(result).toStrictEqual(expectedQuery); + }); -describe('getSearchQueryV2', () => { - it('should form a correct query when all parameters are provided', () => { - const searchText = 'multiple myeloma'; + it('should treat filters with only empty arrays as empty', () => { const filters = { - primary_disease: [ - "Melanoma", - "Multiple Cancer Types", - ], - dataset_source_repo: [ - "CEDCD", - "dbGaP", + primary_disease: [], + dataset_source_repo: [], + }; + const result = queryGenerator.getSearchQueryV2(null, filters, null, []); + expect(result).toStrictEqual(nullOSQuery); + }); + + it('should filter out search terms shorter than 3 chars and ignore punctuation', () => { + const searchText = 'a b c $ foo'; + const expectedQuery = { + _source: false, + query: { + bool: { + must: [ + { + multi_match: { + query: 'foo', + fields: DATASET_SEARCH_FIELDS, + }, + }, + ], + }, + }, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(searchText, null, null, []); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle search and filters, but empty fields and options', () => { + const searchText = 'multiple myeloma'; + const expectedQuery = { + _source: false, + query: { + bool: { + must: [ + { + multi_match: { query: 'multiple', fields: DATASET_SEARCH_FIELDS }, + }, + { + multi_match: { query: 'myeloma', fields: DATASET_SEARCH_FIELDS }, + }, + ], + filter: [ + { + terms: { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types", + ], + }, + }, + { + terms: { + dataset_source_repo: [ + "CEDCD", + "dbGaP", + ], + }, + }, + ], + }, + }, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(searchText, normalFilters, {}, []); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle only returnFields and filters', () => { + const expectedQuery = { + _source: [ + "dataset_title", + "description" ], + query: { + bool: { + filter: [ + { + terms: { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types" + ] + } + }, + { + terms: { + dataset_source_repo: [ + "CEDCD", + "dbGaP" + ] + } + } + ], + must: [] + } + }, + highlight: oSHighlightClause }; + const result = queryGenerator.getSearchQueryV2(null, normalFilters, {}, normalReturnFields); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle options with page = 0 (invalid), should default from = 0', () => { const options = { pageInfo: { - page: 1, - pageSize: 10, + page: 0, + pageSize: 20, }, - sort: { - name: "Dataset", - k: "dataset_title_sort", - v: "asc", + }; + const expectedQuery = { + _source: false, + size: 20, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(null, null, options, []); + // 'from' should be omitted or 0 if page is invalid + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle negative page number gracefully', () => { + const options = { + pageInfo: { + page: -3, + pageSize: 15, }, }; - const returnFields = ['dataset_title', 'description']; - const result = queryGenerator.getSearchQueryV2(searchText, filters, options, returnFields); - expect(result).toStrictEqual(normalOSQuery); + const expectedQuery = { + _source: false, + size: 15, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(null, null, options, []); + expect(result).toStrictEqual(expectedQuery); }); - it('should correctly handle null parameters', () => { - const result = queryGenerator.getSearchQueryV2(null, null, null, null); - expect(result).toStrictEqual(nullOSQuery); + it('should handle negative pageSize gracefully (should not set size/from)', () => { + const options = { + pageInfo: { + page: 2, + pageSize: -10, + }, + }; + const expectedQuery = { + _source: false, + highlight: oSHighlightClause, + }; + const result = queryGenerator.getSearchQueryV2(null, null, options, []); + // Negative size should ignore size/from + expect(result).toStrictEqual(expectedQuery); }); - it('should correctly handle empty parameters', () => { - const result = queryGenerator.getSearchQueryV2("", {}, {}, []); - expect(result).toStrictEqual(nullOSQuery); + it('should handle undefined parameters the same as null for all', () => { + const expectedQuery = nullOSQuery; + // All undefined + const result = queryGenerator.getSearchQueryV2(); + expect(result).toStrictEqual(expectedQuery); + }); + + it('should handle undefined filters', () => { + const result = queryGenerator.getSearchQueryV2('multiple myeloma', undefined, normalOptions, normalReturnFields); + expect(result).toBeDefined(); + expect(result.query.bool.must.length).toBeGreaterThan(0); }); - // it('should handle leading and trailing spaces', () => { - // const result = utils.getSearchableText(' hello world '); - // expect(result).toBe('hello world'); - // }); - - // it('should return empty string when all terms are 2 or fewer characters', () => { - // const result = utils.getSearchableText('a to in at'); - // expect(result).toBe(''); - // }); - - // it('should return empty string for empty input', () => { - // const result = utils.getSearchableText(''); - // expect(result).toBe(''); - // }); - - // it('should return empty string for whitespace only input', () => { - // const result = utils.getSearchableText(' '); - // expect(result).toBe(''); - // }); - - // it('should handle single word longer than 2 characters', () => { - // const result = utils.getSearchableText('test'); - // expect(result).toBe('test'); - // }); - - // it('should filter out single word with 2 or fewer characters', () => { - // const result = utils.getSearchableText('at'); - // expect(result).toBe(''); - // }); - - // it('should handle mixed case text', () => { - // const result = utils.getSearchableText('Hello WORLD Test'); - // expect(result).toBe('Hello WORLD Test'); - // }); - - // it('should preserve special characters in terms longer than 2 characters', () => { - // const result = utils.getSearchableText('test-123 data@2024'); - // expect(result).toBe('test-123 data@2024'); - // }); - - // it('should handle terms with exactly 3 characters', () => { - // const result = utils.getSearchableText('the cat dog'); - // expect(result).toBe('the cat dog'); - // }); - - // it('should filter terms with exactly 2 characters', () => { - // const result = utils.getSearchableText('in on at to my cat'); - // expect(result).toBe('cat'); - // }); - - // it('should filter terms with exactly 1 character', () => { - // const result = utils.getSearchableText('a b c test hello'); - // expect(result).toBe('test hello'); - // }); - - // it('should handle complex mixed input', () => { - // const result = utils.getSearchableText(' a hello to world in test '); - // expect(result).toBe('hello world test'); - // }); + it('should handle undefined options', () => { + const result = queryGenerator.getSearchQueryV2('multiple myeloma', normalFilters, undefined, normalReturnFields); + expect(result).toBeDefined(); + expect(result._source).toEqual(normalReturnFields); + expect(result.query.bool.must.length).toBeGreaterThan(0); + }); + + it('should handle undefined returnFields', () => { + const result = queryGenerator.getSearchQueryV2('multiple myeloma', normalFilters, normalOptions, undefined); + expect(result).toBeDefined(); + expect(result._source).toEqual(false); + expect(result.query.bool.must.length).toBeGreaterThan(0); + }); }); From d9fcdd11ce6ec93d06ac5fb7890417358a2a2c36 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Thu, 15 Jan 2026 17:23:33 +0000 Subject: [PATCH 09/21] Updated test cases for query generator --- Services/queryGenerator.js | 60 +++- Services/queryGenerator.test.fixtures.js | 4 - Services/queryGenerator.test.js | 396 +++++------------------ 3 files changed, 135 insertions(+), 325 deletions(-) diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index 2db0e74..ec061e7 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -194,7 +194,7 @@ queryGenerator.getSortClause = (options) => { queryGenerator.getTextSearchConditions = (searchText) => { // Handle null parameter if (!searchText || typeof searchText !== 'string') { - return []; + return null; } const conditions = []; @@ -207,6 +207,11 @@ queryGenerator.getTextSearchConditions = (searchText) => { return searchTerms.indexOf(term) === idx; }); + // Check again that actual search terms exist + if (uniqueSearchTerms.length <= 0) { + return null; + } + // Add a search condition for finding each term in any of the dataset fields uniqueSearchTerms.forEach((term) => { const dsl = { @@ -222,31 +227,64 @@ queryGenerator.getTextSearchConditions = (searchText) => { return conditions; }; +/** + * Constructs a search query for the datasets index + * @param {String} searchText The text to search for + * @param {Object} filters The filters to apply + * @param {Object} options Sort and pagination options + * @param {Array} returnFields The fields to return + * @returns + */ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) => { - const body = {}; + const body = { + from: 0, + size: 10, + }; const compoundQuery = { 'bool': { - 'must': [], }, }; - const filtersClause = queryGenerator.getFiltersClause(filters); - const textSearchClause = queryGenerator.getTextSearchConditions(searchText); + let filtersClause; + let textSearchClause; + + // Check searchText type + // Loose null equality treats undefined as null + if (searchText != null && typeof searchText !== 'string') { + return null; + } + + // Check filters type + if (typeof filters !== 'object' || Array.isArray(filters)) { + return null; + } + + // Check options type + if (typeof options !== 'object' || Array.isArray(options)) { + return null; + } + + // Check returnFields type and length + if (!Array.isArray(returnFields) || returnFields.length <= 0) { + return null; + } + + filtersClause = queryGenerator.getFiltersClause(filters); + textSearchClause = queryGenerator.getTextSearchConditions(searchText); body['_source'] = returnFields && returnFields.length > 0 ? returnFields : false; if (options && typeof options === 'object' && !Array.isArray(options)) { if (options.pageInfo?.pageSize > 0) { body.size = options.pageInfo.pageSize; + } - if (options.pageInfo.page > 0) { - body.from = body.size * (options.pageInfo.page - 1); - } else { - body.from = 0; - } + if (options.pageInfo?.page > 0) { + body.from = body.size * (options.pageInfo.page - 1); } } const sortClause = queryGenerator.getSortClause(options); + if (sortClause != null) { body.sort = [sortClause]; } @@ -259,7 +297,7 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = compoundQuery.bool.must = textSearchClause; } - if (compoundQuery.bool.must.length > 0 || compoundQuery.bool.filter) { + if (compoundQuery.bool.must?.length > 0 || compoundQuery.bool.filter) { body.query = compoundQuery; } diff --git a/Services/queryGenerator.test.fixtures.js b/Services/queryGenerator.test.fixtures.js index c2e10c8..5b75b81 100644 --- a/Services/queryGenerator.test.fixtures.js +++ b/Services/queryGenerator.test.fixtures.js @@ -78,7 +78,3 @@ export const normalOSQuery = { ], highlight: oSHighlightClause, }; -export const nullOSQuery = { - _source: false, - highlight: oSHighlightClause, -}; diff --git a/Services/queryGenerator.test.js b/Services/queryGenerator.test.js index 3bd0a7b..9d5fd2f 100644 --- a/Services/queryGenerator.test.js +++ b/Services/queryGenerator.test.js @@ -4,33 +4,76 @@ const { DATASET_SEARCH_FIELDS } = require('../Utils/datasetFields.js'); import { normalSearchText, normalFilters, normalReturnFields, normalOptions } from './queryGenerator.test.fixtures.js'; // Opensearch -import { normalOSQuery, nullOSQuery, oSHighlightClause } from './queryGenerator.test.fixtures.js'; +import { normalOSQuery, oSHighlightClause } from './queryGenerator.test.fixtures.js'; describe('getSearchQueryV2', () => { it('should handle undefined parameters', () => { // 1 + // Should be null, mainly because returnFields is undefined const result = queryGenerator.getSearchQueryV2(undefined, undefined, undefined, undefined); - expect(result).toStrictEqual(nullOSQuery); + expect(result).toBeNull(); + + // Undefined searchText is ok + const querySearchText = JSON.parse(JSON.stringify(normalOSQuery)); + delete querySearchText.query.bool.must; + const resultSearchText = queryGenerator.getSearchQueryV2(undefined, normalFilters, normalOptions, normalReturnFields); + expect(resultSearchText).toStrictEqual(querySearchText); + + // TODO: Add tests showing that undefined filters and options are ok }); it('should handle null parameters', () => { // 2 + // Should be null, mainly because returnFields is null const result = queryGenerator.getSearchQueryV2(null, null, null, null); - expect(result).toStrictEqual(nullOSQuery); + expect(result).toBeNull(); + + // TODO: Add tests showing that null searchText, filters and options are ok }); it('should correctly handle empty parameters', () => { // 3 + // Should be null, mainly because returnFields is empty const result = queryGenerator.getSearchQueryV2('', {}, {}, []); - expect(result).toStrictEqual(nullOSQuery); + expect(result).toBeNull(); + + // Empty searchText is ok + const querySearchText = JSON.parse(JSON.stringify(normalOSQuery)); + delete querySearchText.query.bool.must; + const resultSearchText = queryGenerator.getSearchQueryV2('', normalFilters, normalOptions, normalReturnFields); + expect(resultSearchText).toStrictEqual(querySearchText); + + // TODO: Add tests showing that empty filters and options are ok }); - it('should handle searchText being the wrong type', () => { // 4 - // Test with a number - const resultNumber = queryGenerator.getSearchQueryV2(12345, null, null, null); - expect(resultNumber).toStrictEqual(nullOSQuery); + it('should handle parameters being the wrong type', () => { // 4 + // Search text being the wrong type + const resultSearchText = queryGenerator.getSearchQueryV2(12345, normalFilters, normalOptions, normalReturnFields); + expect(resultSearchText).toBeNull(); + + // Filters not being an object + const resultFilters = queryGenerator.getSearchQueryV2(normalSearchText, 'not an object', normalOptions, normalReturnFields); + expect(resultFilters).toBeNull(); + + // Filters being an array + const resultFiltersArray = queryGenerator.getSearchQueryV2(normalSearchText, ['a', 'b'], normalOptions, normalReturnFields); + expect(resultFiltersArray).toBeNull(); + + // Options being the wrong type + const resultOptions = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, 'not an object', normalReturnFields); + expect(resultOptions).toBeNull(); + + // Options being an array + const resultOptionsArray = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, [1,2,3], normalReturnFields); + expect(resultOptionsArray).toBeNull(); + + // Return fields not being an array + const resultReturnFields = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, normalOptions, 'not an array'); + expect(resultReturnFields).toBeNull(); }); it('should handle whitespace-only searchText', () => { // 5 - const result = queryGenerator.getSearchQueryV2(' ', null, null, null); - expect(result).toStrictEqual(nullOSQuery); + const querySearchText = JSON.parse(JSON.stringify(normalOSQuery)); + delete querySearchText.query.bool.must; + const result = queryGenerator.getSearchQueryV2(' ', normalFilters, normalOptions, normalReturnFields); + expect(result).toStrictEqual(querySearchText); }); it('should form a correct query when all parameters are provided', () => { // 6 @@ -38,27 +81,7 @@ describe('getSearchQueryV2', () => { expect(result).toStrictEqual(normalOSQuery); }); - it('should handle filters being the wrong type', () => { // 7 - // Pass filters as a string - const resultString = queryGenerator.getSearchQueryV2(null, 'not an object', null, null); - expect(resultString).toStrictEqual(nullOSQuery); - - // Pass filters as an array - const resultArray = queryGenerator.getSearchQueryV2(null, ['a', 'b'], null, null); - expect(resultArray).toStrictEqual(nullOSQuery); - }); - - it('should handle options being the wrong type', () => { // 8 - // Pass options as a string - const resultString = queryGenerator.getSearchQueryV2(null, null, "not an object", null); - expect(resultString).toStrictEqual(nullOSQuery); - - // Pass options as an array - const resultArray = queryGenerator.getSearchQueryV2(null, null, [1,2,3], null); - expect(resultArray).toStrictEqual(nullOSQuery); - }); - - it('should ignore page options being the wrong type', () => { // 9 + it('should handle page being nonpositive', () => { // 7 const optionsWithZeroPage = { ...normalOptions, pageInfo: { @@ -66,295 +89,48 @@ describe('getSearchQueryV2', () => { page: 0, }, }; - const result = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, optionsWithZeroPage, normalReturnFields); - expect(result).toStrictEqual(normalOSQuery); - }); - - it('should handle search only (no filters/options/fields)', () => { - const searchText = 'multiple myeloma'; - const expectedQuery = { - _source: false, - query: { - bool: { - must: [ - { - multi_match: { - query: 'multiple', - fields: DATASET_SEARCH_FIELDS, - }, - }, - { - multi_match: { - query: 'myeloma', - fields: DATASET_SEARCH_FIELDS, - }, - }, - ], - }, - }, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(searchText, null, null, []); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle filters only (no search/options/fields)', () => { - const expectedQuery = { - _source: false, - query: { - bool: { - filter: [ - { - terms: { - primary_disease: [ - "Melanoma", - "Multiple Cancer Types", - ], - }, - }, - { - terms: { - dataset_source_repo: [ - "CEDCD", - "dbGaP", - ], - }, - }, - ], - must: [], - }, - }, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(null, normalFilters, null, []); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle returnFields only (all else empty)', () => { - const expectedQuery = { - _source: ['dataset_title', 'description'], - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(null, null, null, normalReturnFields); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle options with pagination only', () => { - const options = { - pageInfo: { - page: 3, - pageSize: 25, - }, - }; - const expectedQuery = { - _source: false, - size: 25, - from: 50, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(null, null, options, []); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle options with sorting only', () => { - const options = { - sort: { - k: 'dataset_title_sort', - v: 'desc', - }, - }; - const expectedQuery = { - _source: false, - sort: [ - { - dataset_title_sort: 'desc', - }, - ], - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(null, null, options, []); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should treat filters with only empty arrays as empty', () => { - const filters = { - primary_disease: [], - dataset_source_repo: [], - }; - const result = queryGenerator.getSearchQueryV2(null, filters, null, []); - expect(result).toStrictEqual(nullOSQuery); - }); - - it('should filter out search terms shorter than 3 chars and ignore punctuation', () => { - const searchText = 'a b c $ foo'; - const expectedQuery = { - _source: false, - query: { - bool: { - must: [ - { - multi_match: { - query: 'foo', - fields: DATASET_SEARCH_FIELDS, - }, - }, - ], - }, - }, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(searchText, null, null, []); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle search and filters, but empty fields and options', () => { - const searchText = 'multiple myeloma'; - const expectedQuery = { - _source: false, - query: { - bool: { - must: [ - { - multi_match: { query: 'multiple', fields: DATASET_SEARCH_FIELDS }, - }, - { - multi_match: { query: 'myeloma', fields: DATASET_SEARCH_FIELDS }, - }, - ], - filter: [ - { - terms: { - primary_disease: [ - "Melanoma", - "Multiple Cancer Types", - ], - }, - }, - { - terms: { - dataset_source_repo: [ - "CEDCD", - "dbGaP", - ], - }, - }, - ], - }, - }, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(searchText, normalFilters, {}, []); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle only returnFields and filters', () => { - const expectedQuery = { - _source: [ - "dataset_title", - "description" - ], - query: { - bool: { - filter: [ - { - terms: { - primary_disease: [ - "Melanoma", - "Multiple Cancer Types" - ] - } - }, - { - terms: { - dataset_source_repo: [ - "CEDCD", - "dbGaP" - ] - } - } - ], - must: [] - } - }, - highlight: oSHighlightClause - }; - const result = queryGenerator.getSearchQueryV2(null, normalFilters, {}, normalReturnFields); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle options with page = 0 (invalid), should default from = 0', () => { - const options = { + const optionsWithNegativePage = { + ...normalOptions, pageInfo: { - page: 0, - pageSize: 20, + ...normalOptions.pageInfo, + page: -1, }, }; - const expectedQuery = { - _source: false, - size: 20, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(null, null, options, []); - // 'from' should be omitted or 0 if page is invalid - expect(result).toStrictEqual(expectedQuery); + const result = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, optionsWithZeroPage, normalReturnFields); + expect(result).toStrictEqual(normalOSQuery); + const resultWithNegativePage = queryGenerator.getSearchQueryV2(normalSearchText, normalFilters, optionsWithNegativePage, normalReturnFields); + expect(resultWithNegativePage).toStrictEqual(normalOSQuery); }); - it('should handle negative page number gracefully', () => { - const options = { + it('should ignore page size being nonpositive', () => { // 8 + const optionsWithZeroPageSize = { + ...normalOptions, pageInfo: { - page: -3, - pageSize: 15, + ...normalOptions.pageInfo, + pageSize: 0, }, }; - const expectedQuery = { - _source: false, - size: 15, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(null, null, options, []); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle negative pageSize gracefully (should not set size/from)', () => { - const options = { + const optionsWithNegativePageSize = { + ...normalOptions, pageInfo: { - page: 2, - pageSize: -10, + ...normalOptions.pageInfo, + pageSize: -1, }, }; - const expectedQuery = { - _source: false, - highlight: oSHighlightClause, - }; - const result = queryGenerator.getSearchQueryV2(null, null, options, []); - // Negative size should ignore size/from - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle undefined parameters the same as null for all', () => { - const expectedQuery = nullOSQuery; - // All undefined - const result = queryGenerator.getSearchQueryV2(); - expect(result).toStrictEqual(expectedQuery); - }); - - it('should handle undefined filters', () => { - const result = queryGenerator.getSearchQueryV2('multiple myeloma', undefined, normalOptions, normalReturnFields); - expect(result).toBeDefined(); - expect(result.query.bool.must.length).toBeGreaterThan(0); - }); - - it('should handle undefined options', () => { - const result = queryGenerator.getSearchQueryV2('multiple myeloma', normalFilters, undefined, normalReturnFields); - expect(result).toBeDefined(); - expect(result._source).toEqual(normalReturnFields); - expect(result.query.bool.must.length).toBeGreaterThan(0); - }); - - it('should handle undefined returnFields', () => { - const result = queryGenerator.getSearchQueryV2('multiple myeloma', normalFilters, normalOptions, undefined); - expect(result).toBeDefined(); - expect(result._source).toEqual(false); - expect(result.query.bool.must.length).toBeGreaterThan(0); + const resultZero = queryGenerator.getSearchQueryV2( + normalSearchText, + normalFilters, + optionsWithZeroPageSize, + normalReturnFields + ); + expect(resultZero).toStrictEqual(normalOSQuery); + + const resultNegative = queryGenerator.getSearchQueryV2( + normalSearchText, + normalFilters, + optionsWithNegativePageSize, + normalReturnFields + ); + expect(resultNegative).toStrictEqual(normalOSQuery); }); }); From 36ebdcb3ecffe9987de9f425d2f48471eb06de31 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Wed, 21 Jan 2026 20:14:43 +0000 Subject: [PATCH 10/21] Added null checks to query generator --- Services/queryGenerator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index ec061e7..339d1cf 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -254,12 +254,12 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = } // Check filters type - if (typeof filters !== 'object' || Array.isArray(filters)) { + if (filters != null && (typeof filters !== 'object' || Array.isArray(filters))) { return null; } // Check options type - if (typeof options !== 'object' || Array.isArray(options)) { + if (options != null && (typeof options !== 'object' || Array.isArray(options))) { return null; } From 421fc6cbd378122b507b143aa3dc3be67d3403ad Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Wed, 21 Jan 2026 20:15:08 +0000 Subject: [PATCH 11/21] Initial tests for dataset search endpoint --- Services/dataset.service.js | 33 ++++++++++++++++-- Services/dataset.service.test.fixtures.js | 29 ++++++++++++++++ Services/dataset.service.test.js | 42 +++++++++++++++++++++++ 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 Services/dataset.service.test.fixtures.js create mode 100644 Services/dataset.service.test.js diff --git a/Services/dataset.service.js b/Services/dataset.service.js index d70c2f9..fe03629 100644 --- a/Services/dataset.service.js +++ b/Services/dataset.service.js @@ -13,9 +13,37 @@ const FACET_FILTERS = [ ] const search = async (searchText, filters, options) => { + let query = null; let result = {}; - searchText = searchText.replace(/[^a-zA-Z0-9]+/g, ' '); // Ignore special characters - let searchableText = utils.getSearchableText(searchText); + let searchableText = null; + + // Check searchText type + if (searchText && typeof searchText !== 'string') { + return result; + } + + // Check filters type + if (filters && (typeof filters !== 'object' || Array.isArray(filters))) { + return result; + } + + // Check options type + if (options && (typeof options !== 'object' || Array.isArray(options))) { + return result; + } + + // Format the search text + if (searchText) { + searchText = searchText.replace(/[^a-zA-Z0-9]+/g, ' '); // Ignore special characters + searchableText = utils.getSearchableText(searchText); + } + + query = queryGenerator.getSearchQueryV2(searchText, filters, options, DATASET_RETURN_FIELDS); + + if (query == null) { + return result; + } + if (false && searchableText !== "") { let aggregationKey = cacheKeyGenerator.getAggregationKey(searchableText); let aggregation = cache.getValue(aggregationKey); @@ -32,7 +60,6 @@ const search = async (searchText, filters, options) => { result.aggs = 'all'; } - let query = queryGenerator.getSearchQueryV2(searchText, filters, options, DATASET_RETURN_FIELDS); let searchResults = await elasticsearch.searchWithAggregations(config.indexDS, query); let datasets = searchResults.hits.hits.map((ds) => { if (ds.inner_hits) { diff --git a/Services/dataset.service.test.fixtures.js b/Services/dataset.service.test.fixtures.js new file mode 100644 index 0000000..ccfbfee --- /dev/null +++ b/Services/dataset.service.test.fixtures.js @@ -0,0 +1,29 @@ +/** + * Fixtures for the dataset.service.test.js file + */ + +export const inclusiveSearchText = ''; // Search text that matches all datasets +export const normalSearchText = 'multiple myeloma'; +export const inclusiveFilters = {}; // Filters that match all datasets +export const normalFilters = { + primary_disease: [ + "Melanoma", + "Multiple Cancer Types", + ], + dataset_source_repo: [ + "CEDCD", + "dbGaP", + ], +}; +export const inclusiveOptions = {}; // Options that match all datasets +export const normalOptions = { + pageInfo: { + page: 1, + pageSize: 10, + }, + sort: { + name: "Dataset", + k: "dataset_title_sort", + v: "asc", + }, +}; diff --git a/Services/dataset.service.test.js b/Services/dataset.service.test.js new file mode 100644 index 0000000..dfb62a6 --- /dev/null +++ b/Services/dataset.service.test.js @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +const datasetService = require('./dataset.service.js'); +import { + inclusiveFilters, + inclusiveOptions, + inclusiveSearchText, + normalFilters, + normalOptions, + normalSearchText, +} from './dataset.service.test.fixtures.js'; + +describe('search', () => { + it('should return default results if all parameters are undefined', async () => { + const result = await datasetService.search(undefined, undefined, undefined); + const inclusiveResult = await datasetService.search(inclusiveSearchText, inclusiveFilters, inclusiveOptions); + expect(result).toEqual(inclusiveResult); + }); + + it('should return default results if all parameters are null', async () => { + const result = await datasetService.search(null, null, null); + const inclusiveResult = await datasetService.search(inclusiveSearchText, inclusiveFilters, inclusiveOptions); + expect(result).toEqual(inclusiveResult); + }); + + it('should return the same results for null searchText as for empty searchText', async () => { + const resultNull = await datasetService.search(null, normalFilters, normalOptions); + const resultEmpty = await datasetService.search('', normalFilters, normalOptions); + expect(resultNull).toEqual(resultEmpty); + }); + + it('should return the same results for null filters as for empty filters', async () => { + const resultNull = await datasetService.search(normalSearchText, null, normalOptions); + const resultEmpty = await datasetService.search(normalSearchText, {}, normalOptions); + expect(resultNull).toEqual(resultEmpty); + }); + + it('should return the same results for null options as for empty options', async () => { + const resultNull = await datasetService.search(normalSearchText, normalFilters, null); + const resultEmpty = await datasetService.search(normalSearchText, normalFilters, {}); + expect(resultNull).toEqual(resultEmpty); + }); +}); From f58f15075f74f0216df86ef6016b21e30a1737f1 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Wed, 21 Jan 2026 20:24:10 +0000 Subject: [PATCH 12/21] Added tests to check for data and total count in results --- Services/dataset.service.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Services/dataset.service.test.js b/Services/dataset.service.test.js index dfb62a6..ac90dcb 100644 --- a/Services/dataset.service.test.js +++ b/Services/dataset.service.test.js @@ -10,6 +10,16 @@ import { } from './dataset.service.test.fixtures.js'; describe('search', () => { + it('should have a "data" key in the results object', async () => { + const result = await datasetService.search(normalSearchText, normalFilters, normalOptions); + expect(result).toHaveProperty('data'); + }); + + it('should have a "total" key in the results object', async () => { + const result = await datasetService.search(normalSearchText, normalFilters, normalOptions); + expect(result).toHaveProperty('total'); + }); + it('should return default results if all parameters are undefined', async () => { const result = await datasetService.search(undefined, undefined, undefined); const inclusiveResult = await datasetService.search(inclusiveSearchText, inclusiveFilters, inclusiveOptions); From f97a7ff31566fc2ca8c05ebf1c6550d2d0c0c028 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Wed, 21 Jan 2026 20:48:41 +0000 Subject: [PATCH 13/21] Added dataset service tests for type --- Services/dataset.service.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Services/dataset.service.test.js b/Services/dataset.service.test.js index ac90dcb..019a602 100644 --- a/Services/dataset.service.test.js +++ b/Services/dataset.service.test.js @@ -15,11 +15,21 @@ describe('search', () => { expect(result).toHaveProperty('data'); }); + it('should have the correct type for "data" in the results object', async () => { + const result = await datasetService.search(normalSearchText, normalFilters, normalOptions); + expect(Array.isArray(result.data)).toBe(true); + }); + it('should have a "total" key in the results object', async () => { const result = await datasetService.search(normalSearchText, normalFilters, normalOptions); expect(result).toHaveProperty('total'); }); + it('should have the correct type for "total" in the results object', async () => { + const result = await datasetService.search(normalSearchText, normalFilters, normalOptions); + expect(result.total).toBeTypeOf('number'); + }); + it('should return default results if all parameters are undefined', async () => { const result = await datasetService.search(undefined, undefined, undefined); const inclusiveResult = await datasetService.search(inclusiveSearchText, inclusiveFilters, inclusiveOptions); From 7f4664c6f96a5e28bb27ed77a3ed0e0249da3883 Mon Sep 17 00:00:00 2001 From: JoonLeeNIH <98114532+JoonLeeNIH@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:55:34 -0500 Subject: [PATCH 14/21] Update Services/queryGenerator.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Services/queryGenerator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index 339d1cf..c435465 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -233,7 +233,7 @@ queryGenerator.getTextSearchConditions = (searchText) => { * @param {Object} filters The filters to apply * @param {Object} options Sort and pagination options * @param {Array} returnFields The fields to return - * @returns + * @returns {Object|null} The OpenSearch query body object, or null if validation fails. */ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) => { const body = { From d85257341f3c81d8eb616549d619fed5ae912582 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 26 Jan 2026 12:14:01 -0500 Subject: [PATCH 15/21] Search text is no longer mutated when sanitizing --- Services/dataset.service.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Services/dataset.service.js b/Services/dataset.service.js index fe03629..9e4fe30 100644 --- a/Services/dataset.service.js +++ b/Services/dataset.service.js @@ -34,11 +34,11 @@ const search = async (searchText, filters, options) => { // Format the search text if (searchText) { - searchText = searchText.replace(/[^a-zA-Z0-9]+/g, ' '); // Ignore special characters - searchableText = utils.getSearchableText(searchText); + const sanitizedSearchText = searchText.replace(/[^a-zA-Z0-9]+/g, ' '); // Ignore special characters + searchableText = utils.getSearchableText(sanitizedSearchText); } - query = queryGenerator.getSearchQueryV2(searchText, filters, options, DATASET_RETURN_FIELDS); + query = queryGenerator.getSearchQueryV2(sanitizedSearchText, filters, options, DATASET_RETURN_FIELDS); if (query == null) { return result; @@ -48,7 +48,7 @@ const search = async (searchText, filters, options) => { let aggregationKey = cacheKeyGenerator.getAggregationKey(searchableText); let aggregation = cache.getValue(aggregationKey); if (!aggregation) { - let query = queryGenerator.getSearchAggregationQuery(searchText); + let query = queryGenerator.getSearchAggregationQuery(sanitizedSearchText); let searchResults = await elasticsearch.searchWithAggregations(config.indexDS, query); aggregation = searchResults.aggs.myAgg.buckets; //put in cache for 5 mins From 7470a3e698083520617191cdc0284ba229c1d4e2 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 26 Jan 2026 12:14:41 -0500 Subject: [PATCH 16/21] Removed unnecessary checks and code from query generator --- Services/queryGenerator.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index c435465..afb3190 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -245,6 +245,7 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = }, }; let filtersClause; + let sortClause; let textSearchClause; // Check searchText type @@ -273,7 +274,9 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = body['_source'] = returnFields && returnFields.length > 0 ? returnFields : false; - if (options && typeof options === 'object' && !Array.isArray(options)) { + // We already verified that options is the right type if it's not null + // We must still verify that options is truthy + if (options) { if (options.pageInfo?.pageSize > 0) { body.size = options.pageInfo.pageSize; } @@ -283,7 +286,7 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = } } - const sortClause = queryGenerator.getSortClause(options); + sortClause = queryGenerator.getSortClause(options); if (sortClause != null) { body.sort = [sortClause]; @@ -301,14 +304,6 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = body.query = compoundQuery; } - let agg = {}; - agg.myAgg = {}; - agg.myAgg.terms = {}; - agg.myAgg.terms.field = "dbGaP_phs"; - agg.myAgg.terms.size = 1000; - - // body.aggs = agg; - body.highlight = queryGenerator.getHighlightClause(); return body; From f25f9e462a156f25a7409585a5af553988763edc Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 26 Jan 2026 17:17:52 +0000 Subject: [PATCH 17/21] Fixed a wrong parameter for search --- Services/dataset.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/dataset.service.js b/Services/dataset.service.js index 9e4fe30..d20d51d 100644 --- a/Services/dataset.service.js +++ b/Services/dataset.service.js @@ -38,7 +38,7 @@ const search = async (searchText, filters, options) => { searchableText = utils.getSearchableText(sanitizedSearchText); } - query = queryGenerator.getSearchQueryV2(sanitizedSearchText, filters, options, DATASET_RETURN_FIELDS); + query = queryGenerator.getSearchQueryV2(searchableText, filters, options, DATASET_RETURN_FIELDS); if (query == null) { return result; From a6eeda42be0f12ed00529f15bee74cea380ce432 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 26 Jan 2026 18:07:21 +0000 Subject: [PATCH 18/21] Fixed usage of nonexistent variable --- Services/dataset.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/dataset.service.js b/Services/dataset.service.js index d20d51d..fee79c5 100644 --- a/Services/dataset.service.js +++ b/Services/dataset.service.js @@ -48,7 +48,7 @@ const search = async (searchText, filters, options) => { let aggregationKey = cacheKeyGenerator.getAggregationKey(searchableText); let aggregation = cache.getValue(aggregationKey); if (!aggregation) { - let query = queryGenerator.getSearchAggregationQuery(sanitizedSearchText); + let query = queryGenerator.getSearchAggregationQuery(searchableText); let searchResults = await elasticsearch.searchWithAggregations(config.indexDS, query); aggregation = searchResults.aggs.myAgg.buckets; //put in cache for 5 mins From efc6c97ad9bb593a582a02a6efc9ff78512223b0 Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Mon, 26 Jan 2026 18:19:22 +0000 Subject: [PATCH 19/21] Removed redundant checks and unused imports --- Services/queryGenerator.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Services/queryGenerator.js b/Services/queryGenerator.js index afb3190..d14ab34 100644 --- a/Services/queryGenerator.js +++ b/Services/queryGenerator.js @@ -1,6 +1,3 @@ -const { query } = require("winston"); -const config = require("../Config"); -const { values } = require("lodash"); const { DATASET_SEARCH_FIELDS, DATASET_HIGHLIGHT_FIELDS } = require('../Utils/datasetFields.js'); let queryGenerator = {}; @@ -272,7 +269,7 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) = filtersClause = queryGenerator.getFiltersClause(filters); textSearchClause = queryGenerator.getTextSearchConditions(searchText); - body['_source'] = returnFields && returnFields.length > 0 ? returnFields : false; + body['_source'] = returnFields ?? false; // We already verified that options is the right type if it's not null // We must still verify that options is truthy From 45c4552b1ec1d2c79a694cb8002607035e2b936b Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Wed, 28 Jan 2026 23:01:33 +0000 Subject: [PATCH 20/21] Preliminary Opensearch mocking for tests --- Services/dataset.service.test.js | 36 ++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Services/dataset.service.test.js b/Services/dataset.service.test.js index 019a602..5b64fc8 100644 --- a/Services/dataset.service.test.js +++ b/Services/dataset.service.test.js @@ -1,5 +1,19 @@ -import { describe, it, expect } from 'vitest'; -const datasetService = require('./dataset.service.js'); +import { + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest'; + +// These modules are CommonJS (`require(...)`). Instead of relying on module mocking, +// we `spyOn` the exported functions and replace their implementation, which prevents +// any real OpenSearch calls. +import { createRequire } from "module"; +const require = createRequire(import.meta.url); + +const elasticsearch = require("../Components/elasticsearch"); +const datasetService = require("./dataset.service.js"); import { inclusiveFilters, inclusiveOptions, @@ -9,9 +23,27 @@ import { normalSearchText, } from './dataset.service.test.fixtures.js'; +beforeEach(() => { + vi.restoreAllMocks(); + + // Default mocked response shape expected by `dataset.service.search()`. + // (It reads `searchResults.hits.hits` and `searchResults.hits.total.value`.) + vi.spyOn(elasticsearch, "searchWithAggregations").mockResolvedValue({ + hits: { + total: { value: 2 }, + hits: [ + { _source: { dataset_id: "DS1" }, highlight: {} }, + { _source: { dataset_id: "DS2" }, highlight: {} }, + ], + }, + aggs: {}, + }); +}); + describe('search', () => { it('should have a "data" key in the results object', async () => { const result = await datasetService.search(normalSearchText, normalFilters, normalOptions); + expect(elasticsearch.searchWithAggregations).toHaveBeenCalled(); expect(result).toHaveProperty('data'); }); From 39ee62287939f494fcfb852c44a3c33812c36d2f Mon Sep 17 00:00:00 2001 From: Joon Lee Date: Thu, 5 Feb 2026 19:48:00 +0000 Subject: [PATCH 21/21] Mocked Opensearch response is copied from a real response. --- Services/dataset.service.test.fixtures.js | 153 ++++++++++++++++++++++ Services/dataset.service.test.js | 12 +- 2 files changed, 155 insertions(+), 10 deletions(-) diff --git a/Services/dataset.service.test.fixtures.js b/Services/dataset.service.test.fixtures.js index ccfbfee..c5407ce 100644 --- a/Services/dataset.service.test.fixtures.js +++ b/Services/dataset.service.test.fixtures.js @@ -27,3 +27,156 @@ export const normalOptions = { v: "asc", }, }; + +// Copied from the actual Opensearch response received inside the search() function +// Truncated to only 3 results +export const normalOpensearchResults = { + hits: { + total: { + value: 105, + relation: "eq", + }, + max_score: null, + hits: [ + { + _index: "datasets", + _id: "wgKss5sBxNS2-GapIa8v", + _score: null, + _source: { + dataset_maximum_age_at_baseline: null, + related_terms: "Breast Carcinoma In Situ;Carcinoma, Ductal, Breast;Multifactorial Inheritance;LCIS - lobular carcinoma in situ;LCIS, Lobular Carcinoma In Situ;Lobular Carcinoma In Situ", + description: "

We evaluate the validity of repurposing archival tissue specimens for germline genetic studies. We performed lc-WGS and imputed genotypes on 10 pairs of matching blood and tumor tissue and benchmarked the accuracy of genome-wide genotypes, HLA haplotypes, and several polygenic risk scores (PRSs). The reported results indicate the high accuracy of germline genotypes and haplotypes obtained from archival tissue DNA. Using this methodology, we estimate breast cancer PRS in 36 Ductal carcinoma in situ (DCIS) patients and demonstrate its association with breast cancer subsequent event (BCSE).

A description of this work is available in medRxiv: https://www.medrxiv.org/content/10.1101/2022.03.31.22273116v1

", + dataset_year_enrollment_ended: null, + study_links: "", + dataset_source_id: "phs002865", + limitations_for_reuse: "GRU", + funding_source: "", + related_genes: "", + experimental_approaches: null, + dataset_title: "Accurate Genome-Wide Germline DNA Profiling from Decade-Old Archival Tissue Specimens", + dataset_pmid: "", + dataset_doc: "Office of Data Sharing (ODS)", + related_diseases: "Multifactorial Inheritance;Carcinoma, Ductal, Breast;Breast Neoplasms;Breast Carcinoma In Situ;Sequence Analysis;High-Throughput Nucleotide Sequencing;Genotyping Techniques;Prognosis", + PI_name: "Olivier Harismendy", + dataset_source_repo: "dbGaP", + participant_count: 50, + study_type: "Sequencing", + release_date: "2022-08-25", + dataset_minimum_age_at_baseline: null, + primary_disease: "Multiple Cancer Types", + sample_count: 50, + dataset_source_url: "https://www.ncbi.nlm.nih.gov/projects/gap/cgi-bin/study.cgi?study_id=phs002865", + dataset_year_enrollment_started: null, + institute: null, + assay_method: "WGS", + }, + highlight: { + "description.search": [ + "

We evaluate the validity of repurposing archival tissue specimens for germline genetic studies. We performed lc-WGS and imputed genotypes on 10 pairs of matching blood and tumor tissue and benchmarked the accuracy of genome-wide genotypes, HLA haplotypes, and several polygenic risk scores (PRSs). The reported results indicate the high accuracy of germline genotypes and haplotypes obtained from archival tissue DNA. Using this methodology, we estimate breast cancer PRS in 36 Ductal carcinoma in situ (DCIS) patients and demonstrate its association with breast cancer subsequent event (BCSE).

A description of this work is available in medRxiv: https://www.medrxiv.org/content/10.1101/2022.03.31.22273116v1

", + ], + "primary_disease.search": [ + "Multiple Cancer Types", + ], + }, + sort: [ + "accurategenomewidegermlinednaprofilingfromdecadeoldarchivaltissuespecimens", + ], + }, + { + _index: "datasets", + _id: "-AKss5sBxNS2-GapMLLe", + _score: null, + _source: { + dataset_maximum_age_at_baseline: 69, + related_terms: "Buffy Coat and/or Lymphocytes, Feces, Saliva and/or Buccal, Serum and/or Plasma, Tumor Tissue FFPE, Tumor Tissue Fresh/Frozen, Urine", + description: "The Black Women's Health Study (BWHS) is an ongoing follow-up study for cancer and other serious illnesses in U.S. Black women. The study began in 1995 when 59,000 women (median age, 38) from across the U.S. enrolled by completing health questionnaires. The BWHS has successfully followed participants with biennial questionnaires for data on incident disease and medical, reproductive, behavioral, psychosocial, and socioeconomic factors. Follow-up is also conducted through 24 cancer registries and the National Death Index. Participants' addresses have been linked to U.S. census data and to air pollution data. Cancer diagnoses are validated by pathology data from hospitals and cancer registries. A DNA biorepository was established through collection of cheek cell samples from 26,800 participants. Additionally, approximately 13,000 participants have provided blood samples and 900 have provided tumor tissue. The top priority of the BWHS is to conduct research on diseases that disproportionately affect Black Americans, including cancers, stroke, heart disease, type 2 diabetes, chronic kidney disease, lupus, sarcoidosis, and others.", + dataset_year_enrollment_ended: "1995", + study_links: "http://www.bu.edu/bwhs/", + dataset_source_id: "82", + limitations_for_reuse: "", + funding_source: "", + related_genes: "", + experimental_approaches: null, + dataset_title: "A Follow-up Study for Causes of Cancer in Black Women: Black Women's Health Study", + dataset_pmid: "", + dataset_doc: "Division of Cancer Control and Population Sciences (DCCPS)", + related_diseases: "All Other Cancers, Bladder, Bone, Brain, Cervical carcinoma in situ (CIN II/III, CIS, AIS), Cervix (Squamous cell carcinoma, Adenocarcinoma), Colon, Corpus, body of uterus, Ductal carcinoma in situ of breast, Esophagus, Gall bladder and extrahepatic bile ducts, Hodgkin Lymphoma, Invasive Breast Cancer, Kidney and other unspecified urinary organs, Leukemia, Liver and intrahepatic bile ducts, Lung and bronchus, Melanoma (excluding mucosal sites), Myeloma, No Cancer, Non-Hodgkin Lymphoma, Oropharyngeal, Ovary, fallopian tube, broad ligament, Pancreas, Prostate, Rectum and anus, Small intestine, Stomach, Thyroid", + PI_name: "Julie Palmer, ScD, Lynn Rosenberg, ScD", + dataset_source_repo: "CEDCD", + participant_count: 59000, + study_type: "Etiology", + release_date: null, + dataset_minimum_age_at_baseline: 21, + primary_disease: "Multiple Cancer Types", + sample_count: null, + dataset_source_url: "https://cedcd.nci.nih.gov/cohort?id=82", + dataset_year_enrollment_started: "1995", + institute: null, + assay_method: "", + }, + highlight: { + "description.search": [ + "The Black Women's Health Study (BWHS) is an ongoing follow-up study for cancer and other serious illnesses in U.S. Black women. The study began in 1995 when 59,000 women (median age, 38) from across the U.S. enrolled by completing health questionnaires. The BWHS has successfully followed participants with biennial questionnaires for data on incident disease and medical, reproductive, behavioral, psychosocial, and socioeconomic factors. Follow-up is also conducted through 24 cancer registries and the National Death Index. Participants' addresses have been linked to U.S. census data and to air pollution data. Cancer diagnoses are validated by pathology data from hospitals and cancer registries. A DNA biorepository was established through collection of cheek cell samples from 26,800 participants. Additionally, approximately 13,000 participants have provided blood samples and 900 have provided tumor tissue. The top priority of the BWHS is to conduct research on diseases that disproportionately affect Black Americans, including cancers, stroke, heart disease, type 2 diabetes, chronic kidney disease, lupus, sarcoidosis, and others.", + ], + "primary_disease.search": [ + "Multiple Cancer Types", + ], + "related_diseases.search": [ + "All Other Cancers, Bladder, Bone, Brain, Cervical carcinoma in situ (CIN II/III, CIS, AIS), Cervix (Squamous cell carcinoma, Adenocarcinoma), Colon, Corpus, body of uterus, Ductal carcinoma in situ of breast, Esophagus, Gall bladder and extrahepatic bile ducts, Hodgkin Lymphoma, Invasive Breast Cancer, Kidney and other unspecified urinary organs, Leukemia, Liver and intrahepatic bile ducts, Lung and bronchus, Melanoma (excluding mucosal sites), Myeloma, No Cancer, Non-Hodgkin Lymphoma, Oropharyngeal, Ovary, fallopian tube, broad ligament, Pancreas, Prostate, Rectum and anus, Small intestine, Stomach, Thyroid", + ], + }, + sort: [ + "afollowupstudyforcausesofcancerinblackwomenblackwomenshealthstudy", + ], + }, + { + _index: "datasets", + _id: "7QKss5sBxNS2-GapMLLe", + _score: null, + _source: { + dataset_maximum_age_at_baseline: 64, + related_terms: "Buffy Coat and/or Lymphocytes, Feces, Saliva and/or Buccal, Serum and/or Plasma, Tumor Tissue FFPE, Tumor Tissue Fresh/Frozen, Urine", + description: "This study explores potential causes of cancer and other diseases among farmers and their families and among commercial pesticide applicators. Current medical research suggests that while agricultural workers are generally healthier than the general U.S. population, they may have higher rates of some cancers, including leukemia, myeloma, non-Hodgkin's lymphoma, and cancers of the lip, stomach, skin, brain, and prostate. Other conditions, like asthma, neurologic disease, and adverse reproductive outcomes may also be related to agricultural exposures. The Agricultural Health Study is designed to identify occupational, lifestyle, and genetic factors that may affect the rate of diseases in farming populations. The Agricultural Health Study began in 1994, and will continue to gather information for a number of years about the health of pesticide applicators and their families, details on occupational practices, and information on lifestyle and diet on a periodic basis. The complete set of questionnaires External Web Site Policy may be viewed. Personal identifying information on participants is kept confidential and used only by research staff. Names are not included in any reports. The study results are reported as statistical summaries only. North Carolina and Iowa were selected for this important study based on a nationwide competition. Both states have strong agricultural sectors with diverse production methods, commodities, and products. Information we learn from these two states will be helpful to farmers throughout the United States and other countries using modern agricultural technologies.", + dataset_year_enrollment_ended: "1997", + study_links: "http://aghealth.nih.gov/", + dataset_source_id: "109", + limitations_for_reuse: "", + funding_source: "", + related_genes: "", + experimental_approaches: null, + dataset_title: "Agricultural Health Study", + dataset_pmid: "", + dataset_doc: "Division of Cancer Control and Population Sciences (DCCPS)", + related_diseases: "All Other Cancers, Bladder, Bone, Brain, Cervix (Squamous cell carcinoma, Adenocarcinoma), Colon, Corpus, body of uterus, Esophagus, Gall bladder and extrahepatic bile ducts, Hodgkin Lymphoma, Invasive Breast Cancer, Kidney and other unspecified urinary organs, Leukemia, Liver and intrahepatic bile ducts, Lung and bronchus, Melanoma (excluding mucosal sites), Myeloma, Non-Hodgkin Lymphoma, Oropharyngeal, Ovary, fallopian tube, broad ligament, Pancreas, Prostate, Rectum and anus, Small intestine, Stomach, Thyroid", + PI_name: "Jonathan Hofmann", + dataset_source_repo: "CEDCD", + participant_count: 89656, + study_type: "Etiology", + release_date: null, + dataset_minimum_age_at_baseline: 30, + primary_disease: "Multiple Cancer Types", + sample_count: null, + dataset_source_url: "https://cedcd.nci.nih.gov/cohort?id=109", + dataset_year_enrollment_started: "1993", + institute: null, + assay_method: "", + }, + highlight: { + "description.search": [ + "This study explores potential causes of cancer and other diseases among farmers and their families and among commercial pesticide applicators. Current medical research suggests that while agricultural workers are generally healthier than the general U.S. population, they may have higher rates of some cancers, including leukemia, myeloma, non-Hodgkin's lymphoma, and cancers of the lip, stomach, skin, brain, and prostate. Other conditions, like asthma, neurologic disease, and adverse reproductive outcomes may also be related to agricultural exposures. The Agricultural Health Study is designed to identify occupational, lifestyle, and genetic factors that may affect the rate of diseases in farming populations. The Agricultural Health Study began in 1994, and will continue to gather information for a number of years about the health of pesticide applicators and their families, details on occupational practices, and information on lifestyle and diet on a periodic basis. The complete set of questionnaires External Web Site Policy may be viewed. Personal identifying information on participants is kept confidential and used only by research staff. Names are not included in any reports. The study results are reported as statistical summaries only. North Carolina and Iowa were selected for this important study based on a nationwide competition. Both states have strong agricultural sectors with diverse production methods, commodities, and products. Information we learn from these two states will be helpful to farmers throughout the United States and other countries using modern agricultural technologies.", + ], + "primary_disease.search": [ + "Multiple Cancer Types", + ], + "related_diseases.search": [ + "All Other Cancers, Bladder, Bone, Brain, Cervix (Squamous cell carcinoma, Adenocarcinoma), Colon, Corpus, body of uterus, Esophagus, Gall bladder and extrahepatic bile ducts, Hodgkin Lymphoma, Invasive Breast Cancer, Kidney and other unspecified urinary organs, Leukemia, Liver and intrahepatic bile ducts, Lung and bronchus, Melanoma (excluding mucosal sites), Myeloma, Non-Hodgkin Lymphoma, Oropharyngeal, Ovary, fallopian tube, broad ligament, Pancreas, Prostate, Rectum and anus, Small intestine, Stomach, Thyroid", + ], + }, + sort: [ + "agriculturalhealthstudy", + ], + }, + ], + }, + aggs: undefined, +}; diff --git a/Services/dataset.service.test.js b/Services/dataset.service.test.js index 5b64fc8..4aa0e4e 100644 --- a/Services/dataset.service.test.js +++ b/Services/dataset.service.test.js @@ -21,6 +21,7 @@ import { normalFilters, normalOptions, normalSearchText, + normalOpensearchResults, } from './dataset.service.test.fixtures.js'; beforeEach(() => { @@ -28,16 +29,7 @@ beforeEach(() => { // Default mocked response shape expected by `dataset.service.search()`. // (It reads `searchResults.hits.hits` and `searchResults.hits.total.value`.) - vi.spyOn(elasticsearch, "searchWithAggregations").mockResolvedValue({ - hits: { - total: { value: 2 }, - hits: [ - { _source: { dataset_id: "DS1" }, highlight: {} }, - { _source: { dataset_id: "DS2" }, highlight: {} }, - ], - }, - aggs: {}, - }); + vi.spyOn(elasticsearch, "searchWithAggregations").mockResolvedValue(normalOpensearchResults); }); describe('search', () => {