001package ca.uhn.fhir.jpa.conformance;
002
003/*-
004 * #%L
005 * HAPI FHIR Test Utilities
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.util.CollectionUtil;
024import org.junit.jupiter.params.provider.Arguments;
025
026import javax.annotation.Nonnull;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.InputStreamReader;
030import java.io.LineNumberReader;
031import java.io.Reader;
032import java.nio.charset.StandardCharsets;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.List;
036import java.util.Set;
037import java.util.stream.Collectors;
038
039/**
040 * Collection of test cases for date type search.
041 *
042 * Each test case includes a resource value, a query value, the operator to test, and the expected result.
043 *
044 * @see <a href="https://www.hl7.org/fhir/search.html#date">the spec</a>
045 */
046public class DateSearchTestCase {
047        final String myResourceValue;
048        final String myQueryValue;
049        final boolean expectedResult;
050        final String myFileName;
051        final int myLineNumber;
052
053        public DateSearchTestCase(String myResourceValue, String myQueryValue, boolean expectedResult, String theFileName, int theLineNumber) {
054                this.myResourceValue = myResourceValue;
055                this.myQueryValue = myQueryValue;
056                this.expectedResult = expectedResult;
057                this.myFileName = theFileName;
058                this.myLineNumber = theLineNumber;
059        }
060
061        public Arguments toArguments() {
062                return Arguments.of(myResourceValue, myQueryValue, expectedResult, myFileName, myLineNumber);
063        }
064
065        /**
066         * We have two sources of test cases:
067         * - DateSearchTestCase.csv which holds one test case per line
068         * - DateSearchTestCase-compact.csv which specifies all operators for each value pair
069         */
070        public final static List<DateSearchTestCase> ourCases;
071        static {
072                ourCases = new ArrayList<>();
073                ourCases.addAll(expandedCases());
074                ourCases.addAll(compactCases());
075        }
076
077        private static List<DateSearchTestCase> expandedCases() {
078                String csv = "DateSearchTestCase.csv";
079                InputStream resource = DateSearchTestCase.class.getResourceAsStream(csv);
080                assert resource != null;
081                InputStreamReader inputStreamReader = new InputStreamReader(resource, StandardCharsets.UTF_8);
082                List<DateSearchTestCase> cases = parseCsvCases(inputStreamReader, csv);
083                try {
084                        resource.close();
085                } catch (IOException e) {
086                        e.printStackTrace();
087                }
088                return cases;
089        }
090
091        static List<DateSearchTestCase> parseCsvCases(Reader theSource, String theFileName) {
092                LineNumberReader lineNumberReader = new LineNumberReader(theSource);
093                return lineNumberReader.lines()
094                        .filter(l->!l.startsWith("#")) // strip comments
095                        .map(l -> l.split(","))
096                        .map(fields -> new DateSearchTestCase(fields[0].trim(), fields[1].trim(), Boolean.parseBoolean(fields[2].trim()), theFileName, lineNumberReader.getLineNumber()))
097                        .collect(Collectors.toList());
098        }
099
100        public static List<DateSearchTestCase> compactCases() {
101                String compactCsv = "DateSearchTestCase-compact.csv";
102                InputStream compactStream = DateSearchTestCase.class.getResourceAsStream(compactCsv);
103                assert compactStream != null;
104                return expandPrefixCases(new InputStreamReader(compactStream, StandardCharsets.UTF_8), compactCsv);
105        }
106
107        /**
108         * helper for compressed format of date test cases.
109         * <p>
110         * The csv has rows with: Matching prefixes, Query Date, Resource Date
111         * E.g. "eq ge le,2020, 2020"
112         * This helper expands that one line into test for all of: eq, ge, gt, le, lt, and ne,
113         * expecting the listed prefixes to match, and the unlisted ones to not match.
114         *
115         * @return List of test cases
116         */
117        @Nonnull
118        static List<DateSearchTestCase> expandPrefixCases(Reader theSource, String theFileName) {
119                Set<String> supportedPrefixes = CollectionUtil.newSet("eq", "ge", "gt", "le", "lt", "ne");
120
121                // expand these into individual tests for each prefix.
122                LineNumberReader lineNumberReader = new LineNumberReader(theSource);
123                return lineNumberReader.lines()
124                        .filter(l->!l.startsWith("#")) // strip comments
125                        .map(l -> l.split(","))
126                        .flatMap(fields -> {
127                                // line looks like: "eq ge le,2020, 2020"
128                                // Matching prefixes, Query Date, Resource Date
129                                String resourceValue = fields[0].trim();
130                                String truePrefixes = fields[1].trim();
131                                String queryValue = fields[2].trim();
132                                Set<String> expectedTruePrefixes = Arrays.stream(truePrefixes.split("\\s+")).map(String::trim).collect(Collectors.toSet());
133
134                                // expand to one test case per supportedPrefixes
135                                return supportedPrefixes.stream()
136                                        .map(prefix -> {
137                                                boolean expectMatch = expectedTruePrefixes.contains(prefix);
138                                                return new DateSearchTestCase(resourceValue, prefix + queryValue, expectMatch, theFileName, lineNumberReader.getLineNumber());
139                                        });
140                        })
141                        .collect(Collectors.toList());
142        }
143}