From 5c79fef2556f7ee49ceb847ef038e5d7e5648703 Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Thu, 29 Aug 2024 14:52:08 -0400 Subject: [PATCH] [ALS-6799] Filter by consent - Add consent to filter - Filter by consent for facets, concepts - Tons of tests for facets - Fixed a few bugs while testing --- .../concept/ConceptFilterQueryGenerator.java | 33 +- .../dbmi/avillach/dictionary/facet/Facet.java | 4 + .../dictionary/facet/FacetQueryGenerator.java | 113 +++++-- .../dictionary/facet/FilterPreProcessor.java | 2 +- .../avillach/dictionary/filter/Filter.java | 2 +- .../concept/ConceptControllerTest.java | 2 +- .../concept/ConceptRepositoryTest.java | 16 +- .../concept/ConceptServiceTest.java | 4 +- .../dictionary/facet/FacetControllerTest.java | 2 +- .../facet/FacetQueryGeneratorTest.java | 299 ++++++++++++++++++ .../dictionary/facet/FacetRepositoryTest.java | 6 +- .../dictionary/facet/FacetServiceTest.java | 2 +- .../facet/FilterPreProcessorTest.java | 37 ++- .../ConceptFilterQueryGeneratorTest.java | 41 ++- 14 files changed, 497 insertions(+), 66 deletions(-) create mode 100644 src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGeneratorTest.java diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptFilterQueryGenerator.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptFilterQueryGenerator.java index 205d144..1a9938d 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptFilterQueryGenerator.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptFilterQueryGenerator.java @@ -15,6 +15,14 @@ @Component public class ConceptFilterQueryGenerator { + private static final String CONSENT_QUERY = """ + dataset.dataset_id IN ( + SELECT consent.dataset_id + FROM consent + WHERE consent.consent_code IN (:consents) + ) AND + """; + /** * This generates a query that will return a list of concept_node IDs for the given filter. *

@@ -31,12 +39,15 @@ public QueryParamPair generateFilterQuery(Filter filter, Pageable pageable) { MapSqlParameterSource params = new MapSqlParameterSource(); List clauses = new java.util.ArrayList<>(List.of()); if (!CollectionUtils.isEmpty(filter.facets())) { - clauses.addAll(createFacetFilter(filter.facets(), params, filter.search())); + clauses.addAll(createFacetFilter(filter, params)); } if (StringUtils.hasLength(filter.search())) { params.addValue("search", filter.search().trim()); } - clauses.add(createValuelessNodeFilter(filter.search())); + if (!CollectionUtils.isEmpty(filter.consents())) { + params.addValue("consents", filter.consents()); + } + clauses.add(createValuelessNodeFilter(filter.search(), filter.consents())); String query = "(\n" + String.join("\n\tINTERSECT\n", clauses) + "\n)"; @@ -63,13 +74,14 @@ ORDER BY max(rank) DESC return new QueryParamPair(superQuery, params); } - private String createValuelessNodeFilter(String search) { + private String createValuelessNodeFilter(String search, List consents) { String rankQuery = "0 as rank"; String rankWhere = ""; if (StringUtils.hasLength(search)) { rankQuery = "ts_rank(searchable_fields, (phraseto_tsquery(:search)::text || ':*')::tsquery) as rank"; rankWhere = "concept_node.searchable_fields @@ (phraseto_tsquery(:search)::text || ':*')::tsquery AND"; } + String consentWhere = CollectionUtils.isEmpty(consents) ? "" : CONSENT_QUERY; // concept nodes that have no values and no min/max should not get returned by search return """ SELECT @@ -77,21 +89,24 @@ private String createValuelessNodeFilter(String search) { %s FROM concept_node + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' WHERE + %s %s ( continuous_min.value <> '' OR continuous_max.value <> '' OR categorical_values.value <> '' ) - """.formatted(rankQuery, rankWhere); + """.formatted(rankQuery, rankWhere, consentWhere); } - private List createFacetFilter(List facets, MapSqlParameterSource params, String search) { - return facets.stream() + private List createFacetFilter(Filter filter, MapSqlParameterSource params) { + String consentWhere = CollectionUtils.isEmpty(filter.consents()) ? "" : CONSENT_QUERY; + return filter.facets().stream() .collect(Collectors.groupingBy(Facet::category)) .entrySet().stream() .map(facetsForCategory -> { @@ -101,7 +116,7 @@ private List createFacetFilter(List facets, MapSqlParameterSource .addValue("category_%s".formatted(facetsForCategory.getKey()), facetsForCategory.getKey()); String rankQuery = "0"; String rankWhere = ""; - if (StringUtils.hasLength(search)) { + if (StringUtils.hasLength(filter.search())) { rankQuery = "ts_rank(searchable_fields, (phraseto_tsquery(:search)::text || ':*')::tsquery)"; rankWhere = "concept_node.searchable_fields @@ (phraseto_tsquery(:search)::text || ':*')::tsquery AND"; } @@ -114,13 +129,15 @@ private List createFacetFilter(List facets, MapSqlParameterSource LEFT JOIN facet__concept_node ON facet__concept_node.facet_id = facet.facet_id JOIN facet_category ON facet_category.facet_category_id = facet.facet_category_id JOIN concept_node ON concept_node.concept_node_id = facet__concept_node.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id WHERE + %s %s facet.name IN (:facets_for_category_%s ) AND facet_category.name = :category_%s GROUP BY facet__concept_node.concept_node_id ) - """.formatted(rankQuery, rankWhere, facetsForCategory.getKey(), facetsForCategory.getKey()); + """.formatted(rankQuery, rankWhere, consentWhere, facetsForCategory.getKey(), facetsForCategory.getKey()); }) .toList(); } diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/Facet.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/Facet.java index 1dd7ac4..d328b79 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/Facet.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/Facet.java @@ -13,4 +13,8 @@ public record Facet( public Facet(Facet core, Map meta) { this(core.name(), core.display(), core.description(), core.fullName(), core.count(), core.children(), core.category(), meta); } + + public Facet(String name, String category) { + this(name, "", "", "", null, null, category, null); + } } diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGenerator.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGenerator.java index 7abd75c..b408a67 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGenerator.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGenerator.java @@ -10,32 +10,44 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; @Component public class FacetQueryGenerator { + private static final String CONSENT_QUERY = """ + dataset.dataset_id IN ( + SELECT consent.dataset_id + FROM consent + WHERE consent.consent_code IN (:consents) + ) AND + """; + public String createFacetSQLAndPopulateParams(Filter filter, MapSqlParameterSource params) { Map> groupedFacets = (filter.facets() == null ? Stream.of() : filter.facets().stream()) .collect(Collectors.groupingBy(Facet::category)); + String consentWhere = ""; + if (!CollectionUtils.isEmpty(filter.consents())) { + params.addValue("consents", filter.consents()); + consentWhere = CONSENT_QUERY; + } if (CollectionUtils.isEmpty(filter.facets())) { if (StringUtils.hasLength(filter.search())) { - return createNoFacetSQLWithSearch(filter.search(), params); + return createNoFacetSQLWithSearch(filter.search(), consentWhere, params); } else { - return createNoFacetSQLNoSearch(params); + return createNoFacetSQLNoSearch(params, consentWhere); } } else if (groupedFacets.size() == 1) { if (StringUtils.hasLength(filter.search())) { - return createSingleCategorySQLWithSearch(filter.facets(), filter.search(), params); + return createSingleCategorySQLWithSearch(filter.facets(), filter.search(), consentWhere, params); } else { - return createSingleCategorySQLNoSearch(filter.facets(), params); + return createSingleCategorySQLNoSearch(filter.facets(), consentWhere, params); } } else { if (StringUtils.hasLength(filter.search())) { - return createMultiCategorySQLWithSearch(groupedFacets, filter.search(), params); + return createMultiCategorySQLWithSearch(groupedFacets, filter.search(), consentWhere, params); } else { - return createMultiCategorySQLNoSearch(groupedFacets, params); + return createMultiCategorySQLNoSearch(groupedFacets, consentWhere, params); } } } @@ -48,7 +60,7 @@ private Map createSQLSafeCategoryKeys(List categories) { return keys; } - private String createMultiCategorySQLWithSearch(Map> facets, String search, MapSqlParameterSource params) { + private String createMultiCategorySQLWithSearch(Map> facets, String search, String consentWhere, MapSqlParameterSource params) { Map categoryKeys = createSQLSafeCategoryKeys(facets.keySet().stream().toList()); params.addValue("search", search); @@ -104,15 +116,20 @@ private String createMultiCategorySQLWithSearch(Map> facets, facet.facet_id, count(*) as facet_count FROM facet + LEFT JOIN facet_category fc ON fc.facet_category_id = facet.facet_category_id JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id WHERE - fcn.concept_node_id IN (%s) + %s + fcn.concept_node_id IN (%s) AND + fc.name = :facet_category_%s GROUP BY facet.facet_id ORDER BY facet_count DESC ) - """.formatted(allConceptsForCategory); + """.formatted(consentWhere, allConceptsForCategory, categoryKeys.get(category)); }) .collect(Collectors.joining("\n\tUNION\n")); @@ -132,7 +149,10 @@ facet.facet_id, count(*) as facet_count facet JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id WHERE + %s fc.name NOT IN (:all_selected_facet_categories) AND fcn.concept_node_id IN (%s) GROUP BY @@ -140,12 +160,12 @@ AND fcn.concept_node_id IN (%s) ORDER BY facet_count DESC ) - """.formatted(allConceptsForUnselectedCategories); + """.formatted(consentWhere, allConceptsForUnselectedCategories); return conceptsQuery + selectedFacetsQuery + unselectedFacetsQuery; } - private String createMultiCategorySQLNoSearch(Map> facets, MapSqlParameterSource params) { + private String createMultiCategorySQLNoSearch(Map> facets, String consentWhere, MapSqlParameterSource params) { Map categoryKeys = createSQLSafeCategoryKeys(facets.keySet().stream().toList()); /* @@ -187,13 +207,13 @@ private String createMultiCategorySQLNoSearch(Map> facets, M and INTERSECT them. This creates the concepts for this category */ String selectedFacetsQuery = facets.keySet().stream().map(category -> { - params.addValue("facet_category_" + categoryKeys.get(category), category); - String allConceptsForCategory = categoryKeys.values().stream() - .filter(key -> !categoryKeys.get(category).equals(key)) - .map(key -> "SELECT * FROM facet_category_" + key + "_concepts") - .collect(Collectors.joining(" INTERSECT ")); - params.addValue("", ""); - return """ + params.addValue("facet_category_" + categoryKeys.get(category), category); + String allConceptsForCategory = categoryKeys.values().stream() + .filter(key -> !categoryKeys.get(category).equals(key)) + .map(key -> "SELECT * FROM facet_category_" + key + "_concepts") + .collect(Collectors.joining(" INTERSECT ")); + params.addValue("", ""); + return """ ( SELECT facet.facet_id, count(*) as facet_count @@ -201,7 +221,10 @@ facet.facet_id, count(*) as facet_count facet JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id WHERE + %s fcn.concept_node_id IN (%s) AND fc.name = :facet_category_%s GROUP BY @@ -209,7 +232,7 @@ fcn.concept_node_id IN (%s) ORDER BY facet_count DESC ) - """.formatted(allConceptsForCategory, categoryKeys.get(category)); + """.formatted(consentWhere, allConceptsForCategory, categoryKeys.get(category)); }) .collect(Collectors.joining("\n\tUNION\n")); @@ -229,7 +252,10 @@ facet.facet_id, count(*) as facet_count facet JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id WHERE + %s fc.name NOT IN (:all_selected_facet_categories) AND fcn.concept_node_id IN (%s) GROUP BY @@ -237,12 +263,12 @@ AND fcn.concept_node_id IN (%s) ORDER BY facet_count DESC ) - """.formatted(allConceptsForUnselectedCategories); + """.formatted(consentWhere, allConceptsForUnselectedCategories); return conceptsQuery + selectedFacetsQuery + unselectedFacetsQuery; } - private String createSingleCategorySQLWithSearch(List facets, String search, MapSqlParameterSource params) { + private String createSingleCategorySQLWithSearch(List facets, String search, String consentWhere, MapSqlParameterSource params) { params.addValue("facet_category_name", facets.getFirst().category()); params.addValue("facets", facets.stream().map(Facet::name).toList()); params.addValue("search", search); @@ -262,11 +288,13 @@ facet.facet_id, count(*) as facet_count facet JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id - JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' WHERE + %s fc.name = :facet_category_name AND concept_node.searchable_fields @@ (phraseto_tsquery(:search)::text || ':*')::tsquery AND ( @@ -287,7 +315,8 @@ WITH matching_concepts AS ( FROM facet JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id - JOIN concept_node ON concept_node.concept_node_id = facet__concept_node.concept_node + JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id + JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' @@ -306,19 +335,22 @@ facet.facet_id, count(*) as facet_count FROM facet JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id JOIN matching_concepts ON fcn.concept_node_id = matching_concepts.concept_node_id WHERE + %s fc.name <> :facet_category_name GROUP BY facet.facet_id ORDER BY facet_count DESC ) - """; + """.formatted(consentWhere, consentWhere); } - private String createSingleCategorySQLNoSearch(List facets, MapSqlParameterSource params) { + private String createSingleCategorySQLNoSearch(List facets, String consentWhere, MapSqlParameterSource params) { params.addValue("facet_category_name", facets.getFirst().category()); params.addValue("facets", facets.stream().map(Facet::name).toList()); // return all the facets in the matched category that are displayable @@ -332,11 +364,13 @@ facet.facet_id, count(*) as facet_count facet JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id - JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' WHERE + %s fc.name = :facet_category_name AND ( continuous_min.value <> '' OR @@ -375,19 +409,22 @@ facet.facet_id, count(*) as facet_count FROM facet JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id + LEFT JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id JOIN matching_concepts ON fcn.concept_node_id = matching_concepts.concept_node_id WHERE + %s fc.name <> :facet_category_name GROUP BY facet.facet_id ORDER BY facet_count DESC ) - """; + """.formatted(consentWhere, consentWhere); } - private String createNoFacetSQLWithSearch(String search, MapSqlParameterSource params) { + private String createNoFacetSQLWithSearch(String search, String consentWhere, MapSqlParameterSource params) { // return all the facets that match concepts that // match search // are displayable @@ -400,10 +437,12 @@ facet.facet_id, count(*) as facet_count JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' WHERE + %s concept_node.searchable_fields @@ (phraseto_tsquery(:search)::text || ':*')::tsquery AND ( continuous_min.value <> '' OR @@ -414,11 +453,11 @@ facet.facet_id, count(*) as facet_count facet.facet_id ORDER BY facet_count DESC - """; + """.formatted(consentWhere); } - private String createNoFacetSQLNoSearch(MapSqlParameterSource params) { + private String createNoFacetSQLNoSearch(MapSqlParameterSource params, String consents) { // return all the facets that match displayable concepts // this is the easy one! return """ @@ -429,17 +468,21 @@ facet.facet_id, count(*) as facet_count JOIN facet__concept_node fcn ON fcn.facet_id = facet.facet_id JOIN facet_category fc on fc.facet_category_id = facet.facet_category_id JOIN concept_node ON concept_node.concept_node_id = fcn.concept_node_id + LEFT JOIN dataset ON concept_node.dataset_id = dataset.dataset_id LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' WHERE - continuous_min.value <> '' OR - continuous_max.value <> '' OR - categorical_values.value <> '' + %s + ( + continuous_min.value <> '' OR + continuous_max.value <> '' OR + categorical_values.value <> '' + ) GROUP BY facet.facet_id ORDER BY facet_count DESC - """; + """.formatted(consents); } } diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java index f2fc7dc..594407a 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java @@ -34,7 +34,7 @@ public Object afterBodyRead( Type targetType, Class> converterType ) { if (body instanceof Filter filter && StringUtils.hasLength(filter.search())) { - return new Filter(filter.facets(), filter.search().replaceAll("_", "/")); + return new Filter(filter.facets(), filter.search().replaceAll("_", "/"), filter.consents()); } return body; } diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/Filter.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/Filter.java index 5f69069..77e3c70 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/Filter.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/Filter.java @@ -5,5 +5,5 @@ import java.util.List; -public record Filter(@Nullable List facets, @Nullable String search) { +public record Filter(@Nullable List facets, @Nullable String search, List consents) { } diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java index bc6fe53..fdb6834 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java @@ -39,7 +39,7 @@ void shouldListConcepts() { ); Filter filter = new Filter( List.of(new Facet("questionare", "Questionare", "?", "Questionare", 1, null, "category", null)), - "foo" + "foo", List.of() ); Mockito.when(conceptService.listConcepts(filter, Pageable.ofSize(10).withPage(1))) .thenReturn(expected); diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java index 07f5a4b..4f63d4f 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java @@ -47,14 +47,14 @@ static void mySQLProperties(DynamicPropertyRegistry registry) { @Test void shouldListAllConcepts() { - List actual = subject.getConcepts(new Filter(List.of(), ""), Pageable.unpaged()); + List actual = subject.getConcepts(new Filter(List.of(), "", List.of()), Pageable.unpaged()); Assertions.assertEquals(29, actual.size()); } @Test void shouldListFirstTwoConcepts() { - List actual = subject.getConcepts(new Filter(List.of(), ""), Pageable.ofSize(2).first()); + List actual = subject.getConcepts(new Filter(List.of(), "", List.of()), Pageable.ofSize(2).first()); List expected = List.of( new ContinuousConcept("\\phs000007\\pht000021\\phv00003844\\FL200\\", "phv00003844", "FL200", "phs000007", "# 12 OZ CUPS OF CAFFEINATED COLA / DAY", 0, 3, null), new CategoricalConcept("\\Variant Data Type\\Low coverage WGS\\", "Low coverage WGS", "Low coverage WGS", "1", "Low coverage WGS", List.of("TRUE"), null, null) @@ -65,7 +65,7 @@ void shouldListFirstTwoConcepts() { @Test void shouldListNextTwoConcepts() { - List actual = subject.getConcepts(new Filter(List.of(), ""), Pageable.ofSize(2).first().next()); + List actual = subject.getConcepts(new Filter(List.of(), "", List.of()), Pageable.ofSize(2).first().next()); List expected = List.of( new CategoricalConcept("\\phs002385\\RACEG\\", "RACEG", "RACEG", "phs002385", "Race (regrouped)", List.of("Not Reported"), null, null), new CategoricalConcept("\\Variant Data Type\\Low coverage WGS\\", "Low coverage WGS", "Low coverage WGS", "1", "Low coverage WGS", List.of("TRUE"), null, null) @@ -77,7 +77,7 @@ void shouldListNextTwoConcepts() { @Test void shouldFilterConceptsByFacet() { List actual = - subject.getConcepts(new Filter(List.of(new Facet("phs000007", "", "", "", 1, null, "study_ids_dataset_ids", null)), ""), Pageable.unpaged()); + subject.getConcepts(new Filter(List.of(new Facet("phs000007", "", "", "", 1, null, "study_ids_dataset_ids", null)), "", List.of()), Pageable.unpaged()); List expected = List.of( new ContinuousConcept("\\phs000007\\pht000022\\phv00004260\\FM219\\", "phv00004260", "FM219", "phs000007", "# 12 OZ CUPS OF CAFFEINATED COLA / DAY", 0, 1, null), new ContinuousConcept("\\phs000007\\pht000021\\phv00003844\\FL200\\", "phv00003844", "FL200", "phs000007", "# 12 OZ CUPS OF CAFFEINATED COLA / DAY", 0, 3, null), @@ -89,7 +89,7 @@ void shouldFilterConceptsByFacet() { @Test void shouldFilterBySearch() { - List actual = subject.getConcepts(new Filter(List.of(), "COLA"), Pageable.unpaged()); + List actual = subject.getConcepts(new Filter(List.of(), "COLA", List.of()), Pageable.unpaged()); List expected = List.of( new ContinuousConcept("\\phs000007\\pht000022\\phv00004260\\FM219\\", "phv00004260", "FM219", "phs000007", "# 12 OZ CUPS OF CAFFEINATED COLA / DAY", 0, 1, null), new ContinuousConcept("\\phs000007\\pht000021\\phv00003844\\FL200\\", "phv00003844", "FL200", "phs000007", "# 12 OZ CUPS OF CAFFEINATED COLA / DAY", 0, 3, null), @@ -102,7 +102,7 @@ void shouldFilterBySearch() { @Test void shouldFilterByBothSearchAndFacet() { List actual = - subject.getConcepts(new Filter(List.of(new Facet("phs002715", "", "", "", 1, null, "study_ids_dataset_ids", null)), "phs002715"), Pageable.unpaged()); + subject.getConcepts(new Filter(List.of(new Facet("phs002715", "", "", "", 1, null, "study_ids_dataset_ids", null)), "phs002715", List.of()), Pageable.unpaged()); List expected = List.of( new CategoricalConcept("\\phs002715\\age\\", "AGE_CATEGORY", "age", "phs002715", "Participant's age (category)", List.of("21"), null, null), new CategoricalConcept("\\phs002715\\nsrr_ever_smoker\\", "nsrr_ever_smoker", "nsrr_ever_smoker", "phs002715", "Smoker status", List.of("yes"), null, null) @@ -113,14 +113,14 @@ void shouldFilterByBothSearchAndFacet() { @Test void shouldGetCount() { - long actual = subject.countConcepts(new Filter(List.of(), "")); + long actual = subject.countConcepts(new Filter(List.of(), "", List.of())); Assertions.assertEquals(29L, actual); } @Test void shouldGetCountWithFilter() { - Long actual = subject.countConcepts(new Filter(List.of(new Facet("phs002715", "", "", "", 1, null, "study_ids_dataset_ids", null)), "")); + Long actual = subject.countConcepts(new Filter(List.of(new Facet("phs002715", "", "", "", 1, null, "study_ids_dataset_ids", null)), "", List.of())); Assertions.assertEquals(2L, actual); } diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java index 92a80bf..bf38db8 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java @@ -32,7 +32,7 @@ void shouldListConcepts() { List expected = List.of( new CategoricalConcept("A", "a", "A", "invalid.invalid", null, List.of(), null, null) ); - Filter filter = new Filter(List.of(), ""); + Filter filter = new Filter(List.of(), "", List.of()); Pageable page = Pageable.ofSize(10).first(); Mockito.when(repository.getConcepts(filter, page)) .thenReturn(expected); @@ -44,7 +44,7 @@ void shouldListConcepts() { @Test void shouldCountConcepts() { - Filter filter = new Filter(List.of(), ""); + Filter filter = new Filter(List.of(), "", List.of()); Mockito.when(repository.countConcepts(filter)) .thenReturn(1L); diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetControllerTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetControllerTest.java index 1be266f..99a3995 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetControllerTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetControllerTest.java @@ -31,7 +31,7 @@ void shouldListFacets() { Filter filter = new Filter( List.of(new Facet("questionare", "Questionare", "?", "Examination", 1, null, "category", null)), - "foo" + "foo", List.of() ); Mockito.when(facetService.getFacets(filter)) .thenReturn(List.of(expected)); diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGeneratorTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGeneratorTest.java new file mode 100644 index 0000000..8df45aa --- /dev/null +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetQueryGeneratorTest.java @@ -0,0 +1,299 @@ +package edu.harvard.dbmi.avillach.dictionary.facet; + +import edu.harvard.dbmi.avillach.dictionary.filter.Filter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.MountableFile; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +@Testcontainers +@SpringBootTest +class FacetQueryGeneratorTest { + + @Autowired + NamedParameterJdbcTemplate template; + + @Autowired + FacetQueryGenerator subject; + + @Container + static final PostgreSQLContainer databaseContainer = + new PostgreSQLContainer<>("postgres:16") + .withReuse(true) + .withCopyFileToContainer( + MountableFile.forClasspathResource("seed.sql"), "/docker-entrypoint-initdb.d/seed.sql" + ); + + @DynamicPropertySource + static void mySQLProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", databaseContainer::getJdbcUrl); + registry.add("spring.datasource.username", databaseContainer::getUsername); + registry.add("spring.datasource.password", databaseContainer::getPassword); + registry.add("spring.datasource.db", databaseContainer::getDatabaseName); + } + + record IdCountPair(int facetId, int facetCount) {} + + static class IdCountPairMapper implements RowMapper { + + @Override + public IdCountPair mapRow(ResultSet rs, int rowNum) throws SQLException { + return new IdCountPair(rs.getInt("facet_id"), rs.getInt("facet_count")); + } + } + + @Test + void shouldCountFacetsWithNoSearchAndNoSelectedFacetsAndNoConsents() { + Filter filter = new Filter(List.of(), "", List.of()); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(22, 13), new IdCountPair(31, 3), new IdCountPair(27, 3), + new IdCountPair(26, 3), new IdCountPair(28, 3), new IdCountPair(23, 2), + new IdCountPair(25, 2), new IdCountPair(21, 1), new IdCountPair(20, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsWithNoSearchAndNoSelectedFacetsAndConsents() { + Filter filter = new Filter(List.of(), "", List.of("c2")); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(27, 3), new IdCountPair(20, 1), new IdCountPair(21, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsWithSearchAndNoSelectedFacetsAndNoConsents() { + Filter filter = new Filter(List.of(), "age", List.of()); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(23, 1), + new IdCountPair(25, 1), + new IdCountPair(26, 1), + new IdCountPair(28, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsWithSearchAndNoSelectedFacetsAndConsents() { + Filter filter = new Filter(List.of(), "age", List.of("c1")); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(25, 1), + new IdCountPair(26, 1), + new IdCountPair(28, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsWithSearchAndOneSelectedFacetsAndNoConsents() { + Filter filter = new Filter( + List.of(new Facet("phs002715", "study_ids_dataset_ids")), + "age", List.of() + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(23, 1), + new IdCountPair(25, 1), + new IdCountPair(26, 1), + new IdCountPair(28, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsWithSearchAndOneSelectedFacetsAndConsents() { + Filter filter = new Filter( + List.of(new Facet("phs002715", "study_ids_dataset_ids")), + "age", List.of("c1") + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(25, 1), + new IdCountPair(26, 1), + new IdCountPair(28, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsNoSearchAndOneSelectedFacetsAndNoConsents() { + Filter filter = new Filter( + List.of(new Facet("phs002715", "study_ids_dataset_ids")), + "", List.of() + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(28, 3), + new IdCountPair(26, 3), + new IdCountPair(31, 3), + new IdCountPair(22, 13), + new IdCountPair(23, 2), + new IdCountPair(25, 2), + new IdCountPair(27, 3) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsNoSearchAndOneSelectedFacetsAndConsents() { + Filter filter = new Filter( + List.of(new Facet("phs002715", "study_ids_dataset_ids")), + "", List.of("c2") + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(27, 3) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsWithSearchAndTwoSelectedFacetsInDifferentCatsAndNoConsents() { + Filter filter = new Filter( + List.of( + new Facet("phs000007", "study_ids_dataset_ids"), + new Facet("LOINC", "nsrr_harmonized") + ), + "cola", List.of() + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(21, 1), + new IdCountPair(27, 1), + new IdCountPair(20, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsWithSearchAndTwoSelectedFacetsInDifferentCatsAndConsents() { + Filter filter = new Filter( + List.of( + new Facet("phs000007", "study_ids_dataset_ids"), + new Facet("LOINC", "nsrr_harmonized") + ), + "cola", List.of("c1") + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(21, 1), + new IdCountPair(27, 1), + new IdCountPair(20, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsNoSearchAndTwoSelectedFacetsInDifferentCatsAndNoConsents() { + Filter filter = new Filter( + List.of( + new Facet("phs000007", "study_ids_dataset_ids"), + new Facet("LOINC", "nsrr_harmonized") + ), + "", List.of() + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(21, 1), + new IdCountPair(27, 1), + new IdCountPair(20, 1) + ); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldCountFacetsNoSearchAndTwoSelectedFacetsInDifferentCatsAndConsents() { + Filter filter = new Filter( + List.of( + new Facet("phs000007", "study_ids_dataset_ids"), + new Facet("LOINC", "nsrr_harmonized") + ), + "", List.of("c1") + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + String query = subject.createFacetSQLAndPopulateParams(filter, params); + + List actual = template.query(query, params, new IdCountPairMapper()); + List expected = List.of( + new IdCountPair(21, 1), + new IdCountPair(27, 1), + new IdCountPair(20, 1) + ); + + Assertions.assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepositoryTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepositoryTest.java index 5b77d8e..2413526 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepositoryTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepositoryTest.java @@ -43,7 +43,7 @@ static void mySQLProperties(DynamicPropertyRegistry registry) { @Test void shouldGetAllFacets() { - Filter filter = new Filter(List.of(), ""); + Filter filter = new Filter(List.of(), "", List.of()); List actual = subject.getFacets(filter); @@ -52,7 +52,7 @@ void shouldGetAllFacets() { @Test void shouldFilterFacetsBySearch() { - Filter filter = new Filter(List.of(), "X"); + Filter filter = new Filter(List.of(), "X", List.of()); List actual = subject.getFacets(filter); List expected = List.of( @@ -75,7 +75,7 @@ void shouldFilterFacetsBySearch() { @Test void shouldFilterFacetsByFacet() { - Filter filter = new Filter(List.of(new Facet("bch", "BCH", "Boston Childrens Hospital", "Boston Childrens Hospital", 1, null, "category", null)), ""); + Filter filter = new Filter(List.of(new Facet("bch", "BCH", "Boston Childrens Hospital", "Boston Childrens Hospital", 1, null, "category", null)), "", List.of()); List actual = subject.getFacets(filter); List expected = List.of( diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java index cb5269f..55bad26 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java @@ -22,7 +22,7 @@ class FacetServiceTest { @Test void shouldGetFacets() { - Filter filter = new Filter(List.of(), ""); + Filter filter = new Filter(List.of(), "", List.of()); List expected = List.of(new FacetCategory("n", "d", "", List.of(new Facet("f_n", "f_d", "", "", 1, null, "n", null)))); Mockito.when(repository.getFacets(filter)) diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java index acafe75..628c72e 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java @@ -1,7 +1,42 @@ package edu.harvard.dbmi.avillach.dictionary.facet; -import static org.junit.jupiter.api.Assertions.*; +import edu.harvard.dbmi.avillach.dictionary.filter.Filter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpInputMessage; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.type.SimpleType; +import java.util.List; + +@SpringBootTest class FilterPreProcessorTest { + @Autowired + private FilterPreProcessor subject; + + @Test + void shouldProcessFilter() { + Object processedFilter = subject.afterBodyRead( + new Filter(List.of(), "I_love_underscores", List.of()), + Mockito.mock(HttpInputMessage.class), Mockito.mock(MethodParameter.class), + SimpleType.constructUnsafe(Filter.class), null + ); + + Assertions.assertEquals(new Filter(List.of(), "I/love/underscores", List.of()), processedFilter); + } + + @Test + void shouldNotProcessOtherBodies() { + Object actual = subject.afterBodyRead( + "I'm an object!", + Mockito.mock(HttpInputMessage.class), Mockito.mock(MethodParameter.class), + SimpleType.constructUnsafe(Filter.class), null + ); + + Assertions.assertEquals("I'm an object!", actual); + } } \ No newline at end of file diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/filter/ConceptFilterQueryGeneratorTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/filter/ConceptFilterQueryGeneratorTest.java index cba90e7..3a6ba45 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/filter/ConceptFilterQueryGeneratorTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/filter/ConceptFilterQueryGeneratorTest.java @@ -46,7 +46,7 @@ static void mySQLProperties(DynamicPropertyRegistry registry) { @Test void shouldGenerateForFacetAndSearchNoMatch() { - Filter f = new Filter(List.of(new Facet("phs000007", "FHS", "", "", null, null, "study_ids_dataset_ids", null)), "smoke"); + Filter f = new Filter(List.of(new Facet("phs000007", "FHS", "", "", null, null, "study_ids_dataset_ids", null)), "smoke", List.of()); QueryParamPair pair = subject.generateFilterQuery(f, Pageable.unpaged()); List actual = template.queryForList(pair.query(), pair.params(), Integer.class); @@ -57,7 +57,7 @@ void shouldGenerateForFacetAndSearchNoMatch() { @Test void shouldGenerateForFHSFacet() { - Filter f = new Filter(List.of(new Facet("phs000007", "FHS", "", "", null, null, "study_ids_dataset_ids", null)), ""); + Filter f = new Filter(List.of(new Facet("phs000007", "FHS", "", "", null, null, "study_ids_dataset_ids", null)), "", List.of()); QueryParamPair pair = subject.generateFilterQuery(f, Pageable.unpaged()); List actual = template.queryForList(pair.query(), pair.params(), Integer.class); @@ -66,9 +66,42 @@ void shouldGenerateForFHSFacet() { Assertions.assertEquals(expected, actual); } + @Test + void shouldGenerateForFHSFacetWithConsent1() { + Filter f = new Filter(List.of(new Facet("phs000007", "FHS", "", "", null, null, "study_ids_dataset_ids", null)), "", List.of("c1")); + QueryParamPair pair = subject.generateFilterQuery(f, Pageable.unpaged()); + + List actual = template.queryForList(pair.query(), pair.params(), Integer.class); + List expected = List.of(229, 232, 235); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldGenerateForFHSFacetWithConsent1And2() { + Filter f = new Filter(List.of(new Facet("phs000007", "FHS", "", "", null, null, "study_ids_dataset_ids", null)), "", List.of("c1", "c2")); + QueryParamPair pair = subject.generateFilterQuery(f, Pageable.unpaged()); + + List actual = template.queryForList(pair.query(), pair.params(), Integer.class); + List expected = List.of(229, 232, 235); + + Assertions.assertEquals(expected, actual); + } + + @Test + void shouldGenerateForFHSFacetWithConsent3() { + Filter f = new Filter(List.of(new Facet("phs000007", "FHS", "", "", null, null, "study_ids_dataset_ids", null)), "", List.of("c3")); + QueryParamPair pair = subject.generateFilterQuery(f, Pageable.unpaged()); + + List actual = template.queryForList(pair.query(), pair.params(), Integer.class); + List expected = List.of(); + + Assertions.assertEquals(expected, actual); + } + @Test void shouldGenerateForFacetAndSearchMatch() { - Filter f = new Filter(List.of(new Facet("phs002715", "NSRR", "", "", null, null, "study_ids_dataset_ids", null)), "smoke"); + Filter f = new Filter(List.of(new Facet("phs002715", "NSRR", "", "", null, null, "study_ids_dataset_ids", null)), "smoke", List.of()); QueryParamPair pair = subject.generateFilterQuery(f, Pageable.unpaged()); List actual = template.queryForList(pair.query(), pair.params(), Integer.class); @@ -79,7 +112,7 @@ void shouldGenerateForFacetAndSearchMatch() { @Test void shouldGenerateForNSRRFacet() { - Filter f = new Filter(List.of(new Facet("phs002715", "NSRR", "", "", null, null, "study_ids_dataset_ids", null)), ""); + Filter f = new Filter(List.of(new Facet("phs002715", "NSRR", "", "", null, null, "study_ids_dataset_ids", null)), "", List.of()); QueryParamPair pair = subject.generateFilterQuery(f, Pageable.unpaged()); List actual = template.queryForList(pair.query(), pair.params(), Integer.class);