diff --git a/aggregated-cnd/pom.xml b/aggregated-cnd/pom.xml new file mode 100644 index 0000000000..d79064905d --- /dev/null +++ b/aggregated-cnd/pom.xml @@ -0,0 +1,54 @@ + + + + 4.0.0 + + + io.uhndata.cards + cards-parent + 0.9-SNAPSHOT + + + cards-aggregated-cnd + bundle + CARDS - The aggregated CND files for unit testing + + + + + org.apache.sling + slingfeature-maven-plugin + + + + org.apache.felix + maven-bundle-plugin + true + + + {maven-resources} + SLING-INF/nodetypes/aggregated.cnd + + + + + + + diff --git a/aggregated-cnd/src/main/features/feature.json b/aggregated-cnd/src/main/features/feature.json new file mode 100644 index 0000000000..88b4bc8ba5 --- /dev/null +++ b/aggregated-cnd/src/main/features/feature.json @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +{ + "bundles":[ + { + "id":"${project.groupId}:${project.artifactId}:${project.version}", + "start-order":"25" + } + ] +} diff --git a/aggregated-cnd/src/main/resources/SLING-INF/nodetypes/aggregated.cnd b/aggregated-cnd/src/main/resources/SLING-INF/nodetypes/aggregated.cnd new file mode 100644 index 0000000000..7a88bf8696 --- /dev/null +++ b/aggregated-cnd/src/main/resources/SLING-INF/nodetypes/aggregated.cnd @@ -0,0 +1,98 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + + + + +//----------------------------------------------------------------------------- +// Nodetype for data query handling +[cards:dataQuery] > sling:Folder + // Attributes: + query + + // Properties: + // Hardcode the resource type + - sling:resourceType (STRING) = "cards/dataQuery" mandatory autocreated protected + // Hardcode the resource supertype: the dataQuery is a resource + - sling:resourceSuperType (STRING) = "cards/Resource" mandatory autocreated protected + // Set a default title + - title (String) = "CARDS query" mandatory autocreated + +//----------------------------------------------------------------------------- +// The configuration for the quick search widget. +[cards:QuickSearchConfiguration] > sling:Folder + // Attributes: + + // We can use this in a query. + query + + // Properties: + + // Hardcode the resource type. + - sling:resourceType (STRING) = "cards/QuickSearchConfiguration" mandatory autocreated protected + + // Hardcode the resource supertype: this is a resource. + - sling:resourceSuperType (STRING) = "cards/Resource" mandatory autocreated protected + + // What types of resources will be queried. + // Default: all. + - allowedResourceTypes (STRING) multiple + + // How many results should be displayed. + - limit (long) = '5' + + // Whether to show the total number of results. + // Can be set to 'false' in case of performance issues. + - showTotalRows (BOOLEAN) = 'true' + +//----------------------------------------------------------------------------- +[cards:QueryCache] > nt:unstructured + // Attributes + + // We can query the cache for saved counts. + query + + // Properties + + // Hardcode the resource type. + - sling:resourceType (STRING) = "cards/QueryCache" mandatory autocreated protected + + // Hardcode the resource supertype: each queryCache is a resource. + - sling:resourceSuperType (STRING) = "cards/Resource" mandatory autocreated protected + + // Children + +//----------------------------------------------------------------------------- +// The homepage for the QueryCache space. +[cards:QueryCacheHomepage] > sling:Folder + // Attributes: + + // We can use this homepage in a query. + query + + // Properties: + + // Hardcode the resource type. + - sling:resourceType (STRING) = "cards/QueryCacheHomepage" mandatory autocreated protected + + // Hardcode the resource supertype: the QueryCacheHomepage is a resource homepage. + - sling:resourceSuperType (STRING) = "cards/ResourceHomepage" mandatory autocreated protected + + // Children + + * (cards:QueryCache) diff --git a/modules/data-entry/pom.xml b/modules/data-entry/pom.xml index c2cceffc6a..77f1a20c51 100644 --- a/modules/data-entry/pom.xml +++ b/modules/data-entry/pom.xml @@ -30,6 +30,10 @@ bundle CARDS - Data entry module + + 0.90 + + @@ -68,6 +72,18 @@ + + + maven-compiler-plugin + + + -Werror + + true + true + true + + @@ -135,5 +151,79 @@ javax.servlet javax.servlet-api + + ${project.groupId} + cards-data-model-subjects-api + ${project.version} + test + + + ${project.groupId} + cards-data-model-forms-api + ${project.version} + test + + + ${project.groupId} + cards-aggregated-cnd + ${project.version} + test + + + org.apache.sling + org.apache.sling.resourcebuilder + 1.0.4 + test + + + org.apache.sling + org.apache.sling.testing.sling-mock.core + 3.4.2 + test + + + org.apache.sling + org.apache.sling.testing.sling-mock.junit4 + 3.4.2 + test + + + org.apache.sling + org.apache.sling.testing.sling-mock-oak + 3.1.4-1.40.0 + test + + + org.apache.sling + org.apache.sling.testing.jcr-mock + 1.5.4 + test + + + org.osgi + org.osgi.framework + 1.8.0 + test + + + com.google.guava + guava + 20.0 + test + + + junit + junit + + + org.mockito + mockito-core + + + org.assertj + assertj-core + 3.24.2 + test + diff --git a/modules/data-entry/src/main/java/io/uhndata/cards/DeleteServlet.java b/modules/data-entry/src/main/java/io/uhndata/cards/DeleteServlet.java index dbbed9e7f5..fd4a996360 100644 --- a/modules/data-entry/src/main/java/io/uhndata/cards/DeleteServlet.java +++ b/modules/data-entry/src/main/java/io/uhndata/cards/DeleteServlet.java @@ -538,7 +538,6 @@ private static void sendJsonError(final SlingHttpServletResponse response, int s .writeEnd(); } jsonGen.writeEnd().close(); - response.setStatus(sc); } /** diff --git a/modules/data-entry/src/main/java/io/uhndata/cards/FilterServlet.java b/modules/data-entry/src/main/java/io/uhndata/cards/FilterServlet.java index a135cc4f54..86b6b1329f 100644 --- a/modules/data-entry/src/main/java/io/uhndata/cards/FilterServlet.java +++ b/modules/data-entry/src/main/java/io/uhndata/cards/FilterServlet.java @@ -189,7 +189,7 @@ private void copyFields(JsonObject question, String key, JsonObjectBuilder build if (seenTypes.containsKey(key)) { // If this question already exists, make sure that it has the same dataType String questionType = question.getString("dataType"); - if (seenTypes.get(key) != questionType) { + if (!seenTypes.get(key).equals(questionType)) { // DIFFERENT -- prepend a slightly differently named version String newKey = questionType.concat("|").concat(key); seenTypes.put(newKey, questionType); diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/CountServletTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/CountServletTest.java new file mode 100644 index 0000000000..2e347ccbc2 --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/CountServletTest.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.uhndata.cards; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; + +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.resource.*; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link CountServlet}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class CountServletTest +{ + private static final String NODE_TYPE = "jcr:primaryType"; + private static final String SUBJECT_TYPE = "cards:Subject"; + private static final String QUESTIONNAIRE_TYPE = "cards:Questionnaire"; + private static final String QUESTION_TYPE = "cards:Question"; + private static final String FORM_TYPE = "cards:Form"; + private static final String QUESTIONNAIRE_PROPERTY = "questionnaire"; + private static final String COUNT_PROPERTY = "count"; + private static final String COUNT_TYPE_PROPERTY = "countType"; + private static final String RESOURCE_TYPE_PROPERTY = "resourceType"; + private static final String TEST_FORM_PATH = "/Forms/f1"; + private static final String TEST_SUBJECT_PATH = "/Subjects/r1"; + private static final String TEST_QUESTIONNAIRE_PATH = "/Questionnaires/TestSerializableQuestionnaire"; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @InjectMocks + private CountServlet countServlet; + + private BundleContext slingBundleContext; + + private ResourceResolver resourceResolver; + + @Test + public void doGetForExistingParametersWritesNotEmptyResponse() throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String questionnaireUuid = session.getNode(TEST_QUESTIONNAIRE_PATH).getIdentifier(); + + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource("/Forms")); + request.setRemoteUser("admin"); + request.setParameterMap(Map.of( + "includeallstatus", new String[]{"true"}, + "filternames", new String[]{QUESTIONNAIRE_TYPE}, + "filtervalues", new String[]{questionnaireUuid}, + "filtercomparators", new String[]{"="}, + "filtertypes", new String[]{QUESTIONNAIRE_PROPERTY}, + + "fieldname", new String[]{NODE_TYPE}, + "fieldcomparator", new String[]{"="}, + "fieldvalue", new String[]{FORM_TYPE})); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.countServlet.doGet(request, response); + assertEquals("UTF-8", response.getCharacterEncoding()); + assertEquals("application/json;charset=UTF-8", response.getContentType()); + + JsonReader reader = Json.createReader(new StringReader(response.getOutputAsString())); + JsonObject responseJson = reader.readObject(); + assertTrue(responseJson.containsKey(COUNT_PROPERTY)); + assertEquals(Json.createValue(1), responseJson.get(COUNT_PROPERTY)); + + NodeIterator queryCacheNodeChildren = session.getNode("/QueryCache").getNodes(); + assertTrue(queryCacheNodeChildren.hasNext()); + Node queryCacheNode = queryCacheNodeChildren.nextNode(); + assertTrue(queryCacheNode.hasProperty(COUNT_TYPE_PROPERTY)); + assertEquals("=", queryCacheNode.getProperty(COUNT_TYPE_PROPERTY).getString()); + assertTrue(queryCacheNode.hasProperty(COUNT_PROPERTY)); + assertEquals(1, queryCacheNode.getProperty(COUNT_PROPERTY).getLong()); + assertTrue(queryCacheNode.hasProperty("time")); + assertTrue(queryCacheNode.hasProperty(RESOURCE_TYPE_PROPERTY)); + assertEquals("Forms", queryCacheNode.getProperty(RESOURCE_TYPE_PROPERTY).getString()); + + assertTrue(queryCacheNode.hasProperty("cards:Questionnaire=")); + assertEquals(questionnaireUuid, queryCacheNode.getProperty("cards:Questionnaire=").getString()); + + assertTrue(queryCacheNode.hasProperty(NODE_TYPE + "=")); + assertEquals(FORM_TYPE, queryCacheNode.getProperty(NODE_TYPE + "=").getString()); + } + + @Test + public void doGetForSpecialEmptyFilterWritesEmptyResponse() throws IOException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource("/Forms")); + request.setRemoteUser("admin"); + request.setParameterMap(Map.of("filterempty", new String[]{QUESTIONNAIRE_TYPE})); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.countServlet.doGet(request, response); + assertEquals("UTF-8", response.getCharacterEncoding()); + assertEquals("application/json;charset=UTF-8", response.getContentType()); + + JsonReader reader = Json.createReader(new StringReader(response.getOutputAsString())); + JsonObject responseJson = reader.readObject(); + assertTrue(responseJson.containsKey(COUNT_PROPERTY)); + assertEquals(Json.createValue("0"), responseJson.get(COUNT_PROPERTY)); + } + + @Test + public void doGetForNotSpecialEmptyAndNonEmptyFilterWritesNotEmptyResponse() throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource("/Forms")); + request.setRemoteUser("admin"); + request.setParameterMap(Map.of( + "filterempty", new String[]{QUESTION_TYPE}, + "filternotempty", new String[]{SUBJECT_TYPE})); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.countServlet.doGet(request, response); + assertEquals("UTF-8", response.getCharacterEncoding()); + assertEquals("application/json;charset=UTF-8", response.getContentType()); + + JsonReader reader = Json.createReader(new StringReader(response.getOutputAsString())); + JsonObject responseJson = reader.readObject(); + assertTrue(responseJson.containsKey(COUNT_PROPERTY)); + assertEquals(Json.createValue("0"), responseJson.get(COUNT_PROPERTY)); + + NodeIterator queryCacheNodeChildren = session.getNode("/QueryCache").getNodes(); + assertTrue(queryCacheNodeChildren.hasNext()); + Node queryCacheNode = queryCacheNodeChildren.nextNode(); + assertTrue(queryCacheNode.hasProperty(COUNT_TYPE_PROPERTY)); + assertTrue(queryCacheNode.hasProperty(COUNT_PROPERTY)); + assertTrue(queryCacheNode.hasProperty("time")); + assertTrue(queryCacheNode.hasProperty(RESOURCE_TYPE_PROPERTY)); + assertEquals("=", queryCacheNode.getProperty(COUNT_TYPE_PROPERTY).getString()); + assertEquals(0, queryCacheNode.getProperty(COUNT_PROPERTY).getLong()); + assertEquals("Forms", queryCacheNode.getProperty(RESOURCE_TYPE_PROPERTY).getString()); + + assertTrue(queryCacheNode.hasProperty(SUBJECT_TYPE)); + assertEquals("is not empty", queryCacheNode.getProperty(SUBJECT_TYPE).getString()); + + assertTrue(queryCacheNode.hasProperty(QUESTION_TYPE)); + assertEquals("is empty", queryCacheNode.getProperty(QUESTION_TYPE).getString()); + + } + + @Test + public void doGetForNotAdminRemoteUserWritesError() throws IOException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setRemoteUser("notAdmin"); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.countServlet.doGet(request, response); + assertEquals(403, response.getStatus()); + assertEquals("application/json;charset=UTF-8", response.getContentType()); + + JsonReader reader = Json.createReader(new StringReader(response.getOutputAsString())); + JsonObject responseJson = reader.readObject(); + assertTrue(responseJson.containsKey("status")); + assertEquals(Json.createValue("error"), responseJson.get("status")); + assertTrue(responseJson.containsKey("error")); + assertEquals(Json.createValue("Only admin can perform this operation."), responseJson.get("error")); + } + + @Test + public void doGetForNotAdminRemoteUserCatchesException() throws IOException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setRemoteUser("notAdmin"); + SlingHttpServletResponse response = mock(SlingHttpServletResponse.class); + when(response.getWriter()).thenThrow(new IOException()); + this.countServlet.doGet(request, response); + verify(response).setStatus(403); + verify(response).setContentType("application/json;charset=UTF-8"); + verify(response).getWriter(); + } + + @Before + public void setUp() throws RepositoryException, LoginException + { + this.context.build() + .resource("/Questionnaires", NODE_TYPE, "cards:QuestionnairesHomepage") + .resource("/SubjectTypes", NODE_TYPE, "cards:SubjectTypesHomepage") + .resource("/Subjects", NODE_TYPE, "cards:SubjectsHomepage") + .resource("/Forms", NODE_TYPE, "cards:FormsHomepage") + //.resource("/QueryCache", NODE_TYPE, "sling:Folder") + .resource("/QueryCache", NODE_TYPE, "cards:QueryCacheHomepage") + .commit(); + this.context.load().json("/Questionnaires.json", TEST_QUESTIONNAIRE_PATH); + this.context.load().json("/SubjectTypes.json", "/SubjectTypes/Root"); + this.context.build() + .resource(TEST_SUBJECT_PATH, + NODE_TYPE, SUBJECT_TYPE, + "type", + this.context.resourceResolver().getResource("/SubjectTypes/Root").adaptTo(Node.class)) + .commit(); + final Session session = this.context.resourceResolver().adaptTo(Session.class); + Node subject = session.getNode(TEST_SUBJECT_PATH); + Node questionnaire = session.getNode(TEST_QUESTIONNAIRE_PATH); + Node question = session.getNode(TEST_QUESTIONNAIRE_PATH + "/question_1"); + + this.context.build() + .resource(TEST_FORM_PATH, + NODE_TYPE, FORM_TYPE, + "subject", subject, + QUESTIONNAIRE_PROPERTY, questionnaire) + .resource(TEST_FORM_PATH + "/a1", + NODE_TYPE, "cards:Answer", + "question", question) + .commit(); + + this.slingBundleContext = this.context.bundleContext(); + this.resourceResolver = this.slingBundleContext + .getService(this.slingBundleContext.getServiceReference(ResourceResolverFactory.class)) + .getServiceResourceResolver(null); + } +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/DataImportServletTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/DataImportServletTest.java new file mode 100644 index 0000000000..5ead1481d2 --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/DataImportServletTest.java @@ -0,0 +1,613 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.uhndata.cards; + +import java.io.IOException; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Workspace; +import javax.jcr.query.Query; +import javax.servlet.http.HttpServletResponse; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link DataImportServlet}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class DataImportServletTest +{ + private static final String NODE_TYPE = "jcr:primaryType"; + private static final String SUBJECT_TYPE = "cards:Subject"; + private static final String ANSWER_OPTION_TYPE = "cards:AnswerOption"; + private static final String FORM_TYPE = "cards:Form"; + private static final String QUESTIONNAIRE_PROPERTY = "questionnaire"; + private static final String QUESTION_PROPERTY = "question"; + private static final String SUBJECT_PROPERTY = "subject"; + private static final String RELATED_SUBJECTS_PROPERTY = "relatedSubjects"; + private static final String VALUE_PROPERTY = "value"; + private static final String TYPE_PROPERTY = "type"; + private static final String IDENTIFIER_PROPERTY = "identifier"; + private static final String LABEL_PROPERTY = "label"; + + // Paths + private static final String ROOT_FORM_PATH = "/Forms"; + private static final String TEST_SUBJECT_PATH = "/Subjects/r1"; + private static final String TEST_SUBJECT_2_PATH = "/Subjects/r2"; + private static final String ROOT_SUBJECT_TYPE = "/SubjectTypes/Root"; + private static final String BRANCH_SUBJECT_TYPE = "/SubjectTypes/Root/Branch"; + private static final String PATIENT_SUBJECT_TYPE = "/SubjectTypes/Patient"; + private static final String TEST_QUESTIONNAIRE_PATH = "/Questionnaires/TestSerializableQuestionnaire"; + private static final String TEST_TEXT_QUESTIONNAIRE_PATH = "/Questionnaires/TestTextQuestionnaire"; + + // Parameters + private static final String SUBJECT_TYPE_PARAMETER = ":subjectType"; + private static final String DATA_PARAMETER = ":data"; + private static final String QUESTIONNAIRE_PARAMETER = ":questionnaire"; + private static final String PATCH_PARAMETER = ":patch"; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @InjectMocks + private DataImportServlet dataImportServlet; + + private BundleContext slingBundleContext; + + private ResourceResolver resourceResolver; + + @Test + public void doPostWithoutDataTypeParameterValueSendsError() throws IOException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "true" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); + assertEquals("Required parameter \":data\" missing", response.getStatusMessage()); + } + + @Test + public void doPostWithoutQuestionnaireTypeParameterValueSendsError() throws IOException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, "", + PATCH_PARAMETER, "true" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); + assertEquals("Required parameter \":questionnaire\" missing", response.getStatusMessage()); + } + + @Test + public void doPostWithInvalidQuestionnaireTypeParameterValueSendsError() throws IOException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + String invalidQuestionnaireName = "/Questionnaires/InvalidQuestionnaire"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, "", + QUESTIONNAIRE_PARAMETER, invalidQuestionnaireName, + PATCH_PARAMETER, "true" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); + assertEquals("Invalid questionnaire name " + invalidQuestionnaireName, response.getStatusMessage()); + } + + @Test + public void doPostUpdatesValueInTextAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tText Question\tText Question_notes\r\n" + + "f3\tRoot Subject\tnewValue\tnewNote"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_TEXT_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "true" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + Node form = this.context.resourceResolver().getResource("/Forms/f3").adaptTo(Node.class); + assertEquals("newValue", form.getNode("a1").getProperty(VALUE_PROPERTY).getString()); + assertEquals("newNote", form.getNode("a1").getProperty("note").getString()); + } + + @Test + public void doPostCreatesValueInTextAnswerOfDoubleNestedSection() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tText Question\r\n" + + "f1\tRoot Subject\tnewValue"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "true" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + Node form = this.context.resourceResolver().getResource("/Forms/f1").adaptTo(Node.class); + assertTrue(form.getNode("s1").getNode("a6").hasProperty(VALUE_PROPERTY)); + assertEquals(1, form.getNode("s1").getNode("a6").getProperty(VALUE_PROPERTY).getValues().length); + assertEquals("newValue", + form.getNode("s1").getNode("a6").getProperty(VALUE_PROPERTY).getValues()[0].getString()); + } + + @Test + public void doPostCreatesValueInLongAnswerForChildSubject() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tBranch ID\tLong Question\r\n" + + "f2\tRoot Subject\tBranch Subject\t100"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, new String[]{ROOT_SUBJECT_TYPE, BRANCH_SUBJECT_TYPE}, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "true" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + Node form = this.context.resourceResolver().getResource("/Forms/f2").adaptTo(Node.class); + assertEquals(100, form.getNode("a1").getProperty(VALUE_PROPERTY).getLong()); + } + + @Test + public void doPostCreatesSubjectOfPatientTypeAndCreatesNewFormWithTextValueAnswers() throws IOException, + RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tPatient ID\tText Question\tText2 Question\r\n" + + "f5\tPatient Subject\tnewValue\tnewValue2"; + request.setParameterMap(Map.of( + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node formBySubject = getFormOfPatientSubjectType(); + NodeIterator sectionAnswers = formBySubject.getNodes().nextNode().getNodes(); + assertEquals("newValue", sectionAnswers.nextNode().getProperty(VALUE_PROPERTY).getValues()[0].getString()); + assertEquals("newValue2", sectionAnswers.nextNode().getProperty(VALUE_PROPERTY).getValues()[0].getString()); + } + + @Test + public void doPostCreatesNewFormWithLongValueAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tLong Question\r\n" + + "f5\tRoot2 Subject\t100"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + + assertEquals(100, form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getLong()); + } + + @Test + public void doPostCreatesNewFormWithDoubleValueAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tDouble Question\r\n" + + "f5\tRoot2 Subject\t100"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + + assertEquals(100.0, form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getDouble(), 0); + } + + @Test + public void doPostCreatesNewFormWithDecimalValueAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tDecimal Question\r\n" + + "f5\tRoot2 Subject\t100"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + + assertEquals(BigDecimal.valueOf(100), form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getDecimal()); + } + + @Test + public void doPostCreatesNewFormWithBooleanValueAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tBoolean Question\r\n" + + "f5\tRoot2 Subject\ttrue"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + assertEquals(1, form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getLong()); + } + + @Test + public void doPostCreatesNewFormWithVocabularyValueAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tOptions Question\r\n" + + "f5\tRoot2 Subject\t/Vocabularies/Option1"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + assertEquals("/Vocabularies/Option1", form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getString()); + } + + @Test + public void doPostCreatesNewFormWithVocabularyValueAnswerIgnoresCase() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tOptions Question\r\n" + + "f5\tRoot2 Subject\t/VOCABULARIES/OPTION2"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + assertEquals("/Vocabularies/Option2", form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getString()); + } + + @Test + public void doPostCreatesNewFormWithTimeValueAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tTime Question\r\n" + + "f5\tRoot2 Subject\t01:23:13"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + assertEquals("01:23:13", form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getString()); + } + + @Test + public void doPostCreatesNewFormWithDateValueAnswer() throws IOException, RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + final String date = "2023-01-01"; + String dataCsv = "Identifier\tRoot ID\tDate Question\r\n" + + "f5\tRoot2 Subject\t" + date; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + assertEquals(date, sdf.format(form.getNodes().nextNode().getProperty(VALUE_PROPERTY).getDate().getTime())); + } + + @Test + public void doPostCatchesRepositoryException() throws RepositoryException + { + ResourceResolver resolver = mock(ResourceResolver.class); + Session mockedSession = mock(Session.class); + Workspace mockedWorkspace = mock(Workspace.class); + when(resolver.getResource(eq(ROOT_FORM_PATH))).thenReturn(this.resourceResolver.getResource(ROOT_FORM_PATH)); + when(resolver.getResource(eq("/Subjects"))).thenReturn(this.resourceResolver.getResource("/Subjects")); + when(resolver.adaptTo(Session.class)).thenReturn(mockedSession); + when(mockedSession.getWorkspace()).thenReturn(mockedWorkspace); + when(mockedWorkspace.getQueryManager()).thenThrow(new RepositoryException()); + + MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(resolver, this.slingBundleContext); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + Assertions.assertThatCode(() -> this.dataImportServlet.doPost(request, response)).doesNotThrowAnyException(); + } + + @Test + public void doPostCatchesNumberFormatExceptionAndCreatesNewFormWithAnswerWithoutValue() throws IOException, + RepositoryException + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tRoot ID\tDouble Question\r\n" + + "f5\tRoot2 Subject\tnotParsableValue"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "false" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + this.dataImportServlet.doPost(request, response); + + Node subject = this.context.resourceResolver().getResource(TEST_SUBJECT_2_PATH).adaptTo(Node.class); + Node form = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subject.getIdentifier()); + + assertFalse(form.getNodes().nextNode().hasProperty(VALUE_PROPERTY)); + } + + @Test + public void doPostForUnrealSubject() + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + + String dataCsv = "Identifier\tUnreal ID\tText Question\r\n" + + "f3\tUnreal Subject\tnewValue"; + request.setParameterMap(Map.of( + SUBJECT_TYPE_PARAMETER, ROOT_SUBJECT_TYPE, + DATA_PARAMETER, dataCsv, + QUESTIONNAIRE_PARAMETER, TEST_TEXT_QUESTIONNAIRE_PATH, + PATCH_PARAMETER, "true" + )); + + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + Assertions.assertThatCode(() -> this.dataImportServlet.doPost(request, response)).doesNotThrowAnyException(); + } + + @Before + public void setUp() throws RepositoryException, LoginException + { + final Session session = this.context.resourceResolver().adaptTo(Session.class); + this.context.build() + .resource("/Questionnaires", NODE_TYPE, "cards:QuestionnairesHomepage") + .resource("/SubjectTypes", NODE_TYPE, "cards:SubjectTypesHomepage") + .resource("/Subjects", NODE_TYPE, "cards:SubjectsHomepage") + .resource(ROOT_FORM_PATH, NODE_TYPE, "cards:FormsHomepage") + .resource("/Vocabularies", NODE_TYPE, "sling:Folder") + .commit(); + + this.context.load().json("/Questionnaires.json", TEST_QUESTIONNAIRE_PATH); + this.context.load().json("/TextQuestionnaires.json", TEST_TEXT_QUESTIONNAIRE_PATH); + this.context.load().json("/SubjectTypes.json", ROOT_SUBJECT_TYPE); + this.context.load().json("/SubjectTypesPatient.json", PATIENT_SUBJECT_TYPE); + + this.context.build() + .resource(TEST_SUBJECT_PATH, + NODE_TYPE, SUBJECT_TYPE, + TYPE_PROPERTY, + this.context.resourceResolver().getResource(ROOT_SUBJECT_TYPE).adaptTo(Node.class), + IDENTIFIER_PROPERTY, "Root Subject") + .resource(TEST_SUBJECT_PATH + "/b1", + NODE_TYPE, SUBJECT_TYPE, + TYPE_PROPERTY, + this.context.resourceResolver().getResource(BRANCH_SUBJECT_TYPE).adaptTo(Node.class), + IDENTIFIER_PROPERTY, "Branch Subject") + + .resource(TEST_SUBJECT_2_PATH, + NODE_TYPE, SUBJECT_TYPE, + TYPE_PROPERTY, + this.context.resourceResolver().getResource(ROOT_SUBJECT_TYPE).adaptTo(Node.class), + IDENTIFIER_PROPERTY, "Root2 Subject") + .commit(); + + Node rootSubject = session.getNode(TEST_SUBJECT_PATH); + Node branchSubject = session.getNode(TEST_SUBJECT_PATH + "/b1"); + + this.context.build() + .resource("/Vocabularies/Option1", + NODE_TYPE, ANSWER_OPTION_TYPE, + LABEL_PROPERTY, "Option 1", + VALUE_PROPERTY, "O1") + .resource("/Vocabularies/Option2", + NODE_TYPE, ANSWER_OPTION_TYPE, + LABEL_PROPERTY, "Option 2", + VALUE_PROPERTY, "O2") + + .resource("/Forms/f1", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, rootSubject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(rootSubject).toArray()) + .resource("/Forms/f1/s1", + NODE_TYPE, "cards:AnswerSection", + "section", session.getNode(TEST_QUESTIONNAIRE_PATH + "/section_1")) + .resource("/Forms/f1/s1/a6", + NODE_TYPE, "cards:TextAnswer", + QUESTION_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH + "/section_1/question_6")) + + .resource("/Forms/f2", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, branchSubject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(rootSubject, branchSubject).toArray()) + .resource("/Forms/f2/a1", + NODE_TYPE, "cards:LongAnswer", + QUESTION_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH + "/question_1")) + + .resource("/Forms/f3", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, rootSubject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(rootSubject).toArray()) + .resource("/Forms/f3/a1", + NODE_TYPE, "cards:TextAnswer", + QUESTION_PROPERTY, session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH + "/question_1"), + VALUE_PROPERTY, "12345") + .commit(); + + this.slingBundleContext = this.context.bundleContext(); + this.resourceResolver = this.slingBundleContext + .getService(this.slingBundleContext.getServiceReference(ResourceResolverFactory.class)) + .getServiceResourceResolver(null); + + } + + private Node getFormOfPatientSubjectType() throws RepositoryException + { + Node subjectType = this.context.resourceResolver().getResource(PATIENT_SUBJECT_TYPE).adaptTo(Node.class); + Node subjectBySubjectType = getNodeBySearchParam("Subject", TYPE_PROPERTY, subjectType.getIdentifier()); + assertNotNull(subjectBySubjectType); + + Node formBySubject = getNodeBySearchParam("Form", SUBJECT_PROPERTY, subjectBySubjectType.getIdentifier()); + assertNotNull(formBySubject); + return formBySubject; + } + + private Node getNodeBySearchParam(String nodeType, String searchParamName, String searchParamValue) + throws RepositoryException + { + String query = String.format("select n from [cards:%s] as n where n.%s = '%s'", + nodeType, searchParamName, searchParamValue); + Query queryObj = this.resourceResolver.adaptTo(Session.class).getWorkspace().getQueryManager() + .createQuery(query, "JCR-SQL2"); + queryObj.setLimit(1); + NodeIterator nodeResult = queryObj.execute().getNodes(); + + if (nodeResult.hasNext()) { + return nodeResult.nextNode(); + } + return null; + } + +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/DateUtilsTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/DateUtilsTest.java new file mode 100644 index 0000000000..fcd67854b2 --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/DateUtilsTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.uhndata.cards; + +import java.time.Instant; +import java.util.TimeZone; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link DateUtils}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class DateUtilsTest +{ + @Test + public void getTimezoneForDateString() + { + String timezone = DateUtils.getTimezoneForDateString("2023-01-01"); + String expectedTimezone = TimeZone.getDefault().toZoneId().getRules().getStandardOffset(Instant.now()).getId(); + assertEquals(expectedTimezone, timezone); + } + + @Test + public void getTimezoneForDateStringCatchesException() + { + String timezone = DateUtils.getTimezoneForDateString("notParsable"); + String expectedTimezone = TimeZone.getDefault().toZoneId().getRules().getOffset(Instant.now()).getId(); + assertEquals(expectedTimezone, timezone); + } + +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/DeleteServletTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/DeleteServletTest.java new file mode 100644 index 0000000000..ed64d2c1e7 --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/DeleteServletTest.java @@ -0,0 +1,430 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.uhndata.cards; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.Map; + +import javax.jcr.AccessDeniedException; +import javax.jcr.Node; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Workspace; +import javax.jcr.version.VersionManager; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link DeleteServlet}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class DeleteServletTest +{ + private static final String NODE_TYPE = "jcr:primaryType"; + private static final String SUBJECT_TYPE = "cards:Subject"; + private static final String SUBJECT_TYPE_TYPE = "cards:SubjectType"; + private static final String QUESTIONNAIRE_TYPE = "cards:Questionnaire"; + private static final String FORM_TYPE = "cards:Form"; + private static final String QUESTIONNAIRE_PROPERTY = "questionnaire"; + private static final String QUESTION_PROPERTY = "question"; + private static final String SECTION_PROPERTY = "section"; + private static final String SUBJECT_PROPERTY = "subject"; + private static final String RELATED_SUBJECTS_PROPERTY = "relatedSubjects"; + private static final String TYPE_PROPERTY = "type"; + private static final String IDENTIFIER_PROPERTY = "identifier"; + private static final String LABEL_PROPERTY = "label"; + private static final String STATUS_CODE = "status.code"; + + // Paths + private static final String TEST_SUBJECT_PATH = "/Subjects/r1"; + private static final String ROOT_SUBJECT_TYPE = "/SubjectTypes/Root"; + private static final String BRANCH_SUBJECT_TYPE = "/SubjectTypes/Root/Branch"; + private static final String LEAF_SUBJECT_TYPE = "/SubjectTypes/Root/Branch/Leaf"; + private static final String PATIENT_SUBJECT_TYPE = "/SubjectTypes/Patient"; + private static final String TEST_QUESTIONNAIRE_PATH = "/Questionnaires/TestSerializableQuestionnaire"; + private static final String TEST_TEXT_QUESTIONNAIRE_PATH = "/Questionnaires/TestTextQuestionnaire"; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @InjectMocks + private DeleteServlet deleteServlet; + + private BundleContext slingBundleContext; + + private ResourceResolver resourceResolver; + + @Test + public void doDeleteNotRecursiveForFormResourceWithItsSectionAndAnswerChildren() throws ServletException, + IOException + { + MockSlingHttpServletRequest request = mockServletRequest("/Forms/f1", false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertNull(resolver.getResource("/Forms/f1")); + assertNull(resolver.getResource("/Forms/f1/s1")); + assertNull(resolver.getResource("/Forms/f1/s1/a6")); + } + + @Test + public void doDeleteNotRecursiveForSubjectResourceWithReferencesSendsJsonError() throws ServletException, + IOException + { + MockSlingHttpServletRequest request = mockServletRequest(TEST_SUBJECT_PATH, false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + assertConflictResponseError(response, "This item is referenced in 2 forms."); + assertNotNull(resolver.getResource(TEST_SUBJECT_PATH)); + } + + @Test + public void doDeleteNotRecursiveForSubjectTypeResourceWithMultipleTypesReferencesSendsJsonError() + throws ServletException, IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + Node questionnaire = session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH); + Node rootSubjectType = session.getNode(ROOT_SUBJECT_TYPE); + questionnaire.setProperty("requiredSubjectTypes", rootSubjectType.getIdentifier(), PropertyType.REFERENCE); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_TYPE, false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + assertConflictResponseError(response, + "This item is referenced in 2 forms and 2 subjects (Root Subject and Branch Subject)."); + assertNotNull(resolver.getResource(ROOT_SUBJECT_TYPE)); + } + + @Test + public void doDeleteNotRecursiveForSectionResourceWithReferencesSendsJsonError() throws ServletException, + IOException + { + MockSlingHttpServletRequest request = mockServletRequest(TEST_QUESTIONNAIRE_PATH + "/section_1", false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + assertConflictResponseError(response, "This item is referenced in 1 answer section."); + assertNotNull(resolver.getResource(TEST_QUESTIONNAIRE_PATH + "/section_1")); + } + + @Test + public void doDeleteNotRecursiveForQuestionResourceWithReferencesSendsJsonError() throws ServletException, + IOException + { + MockSlingHttpServletRequest request = mockServletRequest(TEST_QUESTIONNAIRE_PATH + "/section_1/question_6", + false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + assertConflictResponseError(response, "This item is referenced in 1 answer."); + assertNotNull(resolver.getResource(TEST_QUESTIONNAIRE_PATH + "/section_1/question_6")); + } + + @Test + public void doDeleteNotRecursiveForSubjectTypeResourceWithReferencesInSubjectSendsJsonError() + throws ServletException, IOException + { + MockSlingHttpServletRequest request = mockServletRequest(BRANCH_SUBJECT_TYPE, false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + assertConflictResponseError(response, "This item is referenced in 1 subject (Branch Subject)."); + assertNotNull(resolver.getResource(BRANCH_SUBJECT_TYPE)); + } + + @Test + public void doDeleteNotRecursiveForSubjectTypeResourceWithReferencesInSubjectTypeSendsJsonError() + throws ServletException, IOException + { + this.context.build() + .resource(PATIENT_SUBJECT_TYPE + "/Visit", + NODE_TYPE, SUBJECT_TYPE_TYPE, + LABEL_PROPERTY, "Visit", + "subjectListLabel", "Visits", + "cards:defaultOrder", 1, + "reference", + this.context.resourceResolver().getResource(LEAF_SUBJECT_TYPE).adaptTo(Node.class)) + .commit(); + MockSlingHttpServletRequest request = mockServletRequest(LEAF_SUBJECT_TYPE, false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + assertConflictResponseError(response, "This item is referenced in 1 subject type (Visit)."); + assertNotNull(resolver.getResource(LEAF_SUBJECT_TYPE)); + } + + @Test + public void doDeleteNotRecursiveForSubjectTypeResourceWithReferencesInQuestionnaireSendsJsonError() + throws ServletException, IOException + { + this.context.build() + .resource("/Questionnaires/TestReferenceQuestionnaire", + NODE_TYPE, QUESTIONNAIRE_TYPE, + "title", "Test Reference Questionnaire", + "description", "A test reference questionnaire", + "requiredSubjectTypes", + this.context.resourceResolver().getResource(LEAF_SUBJECT_TYPE).adaptTo(Node.class)) + .commit(); + MockSlingHttpServletRequest request = mockServletRequest(LEAF_SUBJECT_TYPE, false); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + assertConflictResponseError(response, + "This item is referenced in 1 questionnaire (Test Reference Questionnaire)."); + assertNotNull(resolver.getResource(LEAF_SUBJECT_TYPE)); + } + + @Test + public void doDeleteRecursiveForQuestionnaireResourceWithReferences() throws ServletException, IOException + { + MockSlingHttpServletRequest request = mockServletRequest(TEST_TEXT_QUESTIONNAIRE_PATH, true); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + ResourceResolver resolver = this.context.resourceResolver(); + this.deleteServlet.doDelete(request, response); + assertNull(resolver.getResource(TEST_TEXT_QUESTIONNAIRE_PATH)); + assertNull(resolver.getResource("/Forms/f2")); + } + + @Test + public void doDeleteCatchesRepositoryExceptionAndSendsJsonError() throws RepositoryException, ServletException, + IOException + { + ResourceResolver resolver = mock(ResourceResolver.class); + Session mockedSession = mock(Session.class); + Workspace mockedWorkspace = mock(Workspace.class); + when(resolver.adaptTo(Session.class)).thenReturn(mockedSession); + when(mockedSession.getWorkspace()).thenReturn(mockedWorkspace); + when(mockedWorkspace.getVersionManager()).thenThrow(new RepositoryException()); + + MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(resolver, this.slingBundleContext); + request.setResource(this.resourceResolver.getResource("/Forms/f1")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + + assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus()); + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(STATUS_CODE)); + assertEquals(500, responseJson.getInt(STATUS_CODE)); + + assertTrue(responseJson.containsKey("error")); + JsonObject error = responseJson.get("error").asJsonObject(); + assertEquals(2, error.size()); + assertTrue(error.containsKey("message")); + assertTrue(error.containsKey("class")); + assertEquals("javax.jcr.RepositoryException", error.getString("class")); + } + + @Test + public void doDeleteForEmptyRemoteUserCatchesAccessDeniedExceptionAndSendsJsonError() throws RepositoryException, + ServletException, IOException + { + ResourceResolver resolver = mock(ResourceResolver.class); + Session mockedSession = mock(Session.class); + Workspace mockedWorkspace = mock(Workspace.class); + VersionManager mockedVersionManager = mock(VersionManager.class); + when(resolver.adaptTo(Session.class)).thenReturn(mockedSession); + when(mockedSession.getWorkspace()).thenReturn(mockedWorkspace); + when(mockedWorkspace.getVersionManager()).thenReturn(mockedVersionManager); + doThrow(new AccessDeniedException()).when(mockedSession).save(); + + MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(resolver, this.slingBundleContext); + request.setParameterMap(Map.of("recursive", false)); + request.setResource(this.resourceResolver.getResource("/Forms/f1")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(STATUS_CODE)); + assertEquals(401, responseJson.getInt(STATUS_CODE)); + } + + @Test + public void doDeleteForNotEmptyRemoteUserCatchesAccessDeniedExceptionAndSendsJsonError() throws RepositoryException, + ServletException, IOException + { + ResourceResolver resolver = mock(ResourceResolver.class); + Session mockedSession = mock(Session.class); + Workspace mockedWorkspace = mock(Workspace.class); + VersionManager mockedVersionManager = mock(VersionManager.class); + when(resolver.adaptTo(Session.class)).thenReturn(mockedSession); + when(mockedSession.getWorkspace()).thenReturn(mockedWorkspace); + when(mockedWorkspace.getVersionManager()).thenReturn(mockedVersionManager); + doThrow(new AccessDeniedException()).when(mockedSession).save(); + + MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(resolver, this.slingBundleContext); + request.setParameterMap(Map.of("recursive", false)); + request.setResource(this.resourceResolver.getResource("/Forms/f1")); + request.setRemoteUser("notAdmin"); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.deleteServlet.doDelete(request, response); + assertCharacterEncodingAndContentType(response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(STATUS_CODE)); + assertEquals(403, responseJson.getInt(STATUS_CODE)); + } + + @Before + public void setUp() throws RepositoryException, LoginException + { + final Session session = this.context.resourceResolver().adaptTo(Session.class); + this.context.build() + .resource("/Questionnaires", NODE_TYPE, "cards:QuestionnairesHomepage") + .resource("/SubjectTypes", NODE_TYPE, "cards:SubjectTypesHomepage") + .resource("/Subjects", NODE_TYPE, "cards:SubjectsHomepage") + .resource("/Forms", NODE_TYPE, "cards:FormsHomepage") + .commit(); + + this.context.load().json("/Questionnaires.json", TEST_QUESTIONNAIRE_PATH); + this.context.load().json("/TextQuestionnaires.json", TEST_TEXT_QUESTIONNAIRE_PATH); + this.context.load().json("/SubjectTypes.json", ROOT_SUBJECT_TYPE); + this.context.load().json("/SubjectTypesPatient.json", PATIENT_SUBJECT_TYPE); + + this.context.build() + .resource(TEST_SUBJECT_PATH, + NODE_TYPE, SUBJECT_TYPE, + TYPE_PROPERTY, + this.context.resourceResolver().getResource(ROOT_SUBJECT_TYPE).adaptTo(Node.class), + IDENTIFIER_PROPERTY, "Root Subject") + .resource(TEST_SUBJECT_PATH + "/b1", + NODE_TYPE, SUBJECT_TYPE, + TYPE_PROPERTY, + this.context.resourceResolver().getResource(BRANCH_SUBJECT_TYPE).adaptTo(Node.class), + IDENTIFIER_PROPERTY, "Branch Subject") + .commit(); + + Node rootSubject = session.getNode(TEST_SUBJECT_PATH); + + this.context.build() + .resource("/Forms/f1", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, rootSubject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(rootSubject).toArray()) + .resource("/Forms/f1/s1", + NODE_TYPE, "cards:AnswerSection", + SECTION_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH + "/section_1")) + .resource("/Forms/f1/s1/a6", + NODE_TYPE, "cards:TextAnswer", + QUESTION_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH + "/section_1/question_6")) + .resource("/Forms/f2", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, rootSubject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(rootSubject).toArray()) + .commit(); + + this.slingBundleContext = this.context.bundleContext(); + this.resourceResolver = this.slingBundleContext + .getService(this.slingBundleContext.getServiceReference(ResourceResolverFactory.class)) + .getServiceResourceResolver(null); + } + + + private MockSlingHttpServletRequest mockServletRequest(String resourcePath, boolean isRecursive) + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource(resourcePath)); + request.setParameterMap(Map.of( + "recursive", isRecursive + )); + return request; + } + + private JsonObject getResponseJsonReader(MockSlingHttpServletResponse response) + { + JsonReader reader = Json.createReader(new StringReader(response.getOutputAsString())); + return reader.readObject(); + } + + private void assertConflictResponseError(MockSlingHttpServletResponse response, String expectedMessage) + { + assertEquals(HttpServletResponse.SC_CONFLICT, response.getStatus()); + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(STATUS_CODE)); + assertEquals(409, responseJson.getInt(STATUS_CODE)); + assertTrue(responseJson.containsKey("status.message")); + assertEquals(expectedMessage, responseJson.getString("status.message")); + } + + + private void assertCharacterEncodingAndContentType(MockSlingHttpServletResponse response) + { + assertEquals("UTF-8", response.getCharacterEncoding()); + assertEquals("application/json;charset=UTF-8", response.getContentType()); + } + +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/FilterServletTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/FilterServletTest.java new file mode 100644 index 0000000000..5074bb9509 --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/FilterServletTest.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.uhndata.cards; + +import java.io.IOException; +import java.io.StringReader; +import java.text.SimpleDateFormat; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonReader; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Unit tests for {@link FilterServlet}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class FilterServletTest +{ + private static final String NODE_TYPE = "jcr:primaryType"; + private static final String QUESTIONNAIRE_PROPERTY = "questionnaire"; + private static final String QUESTION_PROPERTY = "question"; + private static final String SECTION_PROPERTY = "section"; + private static final String SUBJECT_PROPERTY = "subject"; + + // Paths + private static final String TEST_QUESTIONNAIRE_PATH = "/Questionnaires/TestSerializableQuestionnaire"; + private static final String TEST_TEXT_QUESTIONNAIRE_PATH = "/Questionnaires/TestTextQuestionnaire"; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @InjectMocks + private FilterServlet filterServlet; + + private BundleContext slingBundleContext; + + private ResourceResolver resourceResolver; + + @Test + public void doGetForRequestWithQuestionnaireParameterWithoutDeepJsonSuffix() throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest("/Questionnaires"); + request.setParameterMap(Map.of( + QUESTIONNAIRE_PROPERTY, TEST_TEXT_QUESTIONNAIRE_PATH + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.filterServlet.doGet(request, response); + JsonObject jsonObject = getResponseJsonReader(response); + assertNotNull(jsonObject); + assertEquals(3, jsonObject.keySet().size()); + } + + @Test + public void doGetForRequestWithQuestionnaireParameterWithDeepJsonSuffix() throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest("/Questionnaires"); + request.setParameterMap(Map.of( + QUESTIONNAIRE_PROPERTY, TEST_TEXT_QUESTIONNAIRE_PATH + ".deep.json" + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.filterServlet.doGet(request, response); + JsonObject jsonObject = getResponseJsonReader(response); + assertNotNull(jsonObject); + assertEquals(3, jsonObject.keySet().size()); + } + + @Test + public void doGetForRequestWithoutQuestionnaireParameter() throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest("/Questionnaires"); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.filterServlet.doGet(request, response); + JsonObject jsonObject = getResponseJsonReader(response); + assertNotNull(jsonObject); + assertEquals(11, jsonObject.keySet().size()); + } + + @Before + public void setUp() throws LoginException + { + this.context.build() + .resource("/Questionnaires", NODE_TYPE, "cards:QuestionnairesHomepage") + .commit(); + + this.context.load().json("/Questionnaires.json", TEST_QUESTIONNAIRE_PATH); + this.context.load().json("/TextQuestionnaires.json", TEST_TEXT_QUESTIONNAIRE_PATH); + + this.context.registerAdapter(Resource.class, JsonObject.class, (Function) resource -> { + JsonObjectBuilder jsonObject = null; + try { + jsonObject = Json.createObjectBuilder(createPropertiesAndChildrenMap(resource)); + } catch (RepositoryException e) { + throw new RuntimeException(e); + } + return jsonObject.build(); + }); + + this.slingBundleContext = this.context.bundleContext(); + this.resourceResolver = this.slingBundleContext + .getService(this.slingBundleContext.getServiceReference(ResourceResolverFactory.class)) + .getServiceResourceResolver(null); + } + + private Map createPropertiesAndChildrenMap(Resource originalResource) throws RepositoryException + { + Map propertiesAndChildrenMap = new HashMap<>(); + + // process properties of resource + ValueMap valueMap = originalResource.getValueMap(); + List objectTypeProperties = List.of(QUESTIONNAIRE_PROPERTY, SUBJECT_PROPERTY, SECTION_PROPERTY, + QUESTION_PROPERTY); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + for (Map.Entry property : valueMap.entrySet()) { + String key = property.getKey(); + Object value = property.getValue(); + if (objectTypeProperties.contains(key)) { + Resource reference = + this.context.resourceResolver().getResource(getResourcePathByItsIdentifier((String) value)); + JsonObjectBuilder referenceJson = Json.createObjectBuilder(createPropertiesAndChildrenMap(reference)); + propertiesAndChildrenMap.put(key, referenceJson.build()); + } else { + if (value.getClass().isArray()) { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + for (Object valueUnit : (Object[]) value) { + if (valueUnit instanceof Resource) { + arrayBuilder.add(Json.createObjectBuilder( + createPropertiesAndChildrenMap((Resource) valueUnit)).build()); + } else { + arrayBuilder.add((String) valueUnit); + } + } + propertiesAndChildrenMap.put(key, arrayBuilder.build()); + } else if (value instanceof GregorianCalendar) { + sdf.setTimeZone(((GregorianCalendar) value).getTimeZone()); + value = ((GregorianCalendar) value).getTime(); + propertiesAndChildrenMap.put(key, sdf.format(value)); + } else { + propertiesAndChildrenMap.put(key, value); + } + } + } + + // process children of resource + for (Resource child : originalResource.getChildren()) { + JsonObject jsonObject = Json.createObjectBuilder(createPropertiesAndChildrenMap(child)).build(); + propertiesAndChildrenMap.put(child.getName(), jsonObject); + } + return propertiesAndChildrenMap; + } + + private String getResourcePathByItsIdentifier(String identifier) throws RepositoryException + { + return this.context.resourceResolver().adaptTo(Session.class).getNodeByIdentifier(identifier).getPath(); + } + + private MockSlingHttpServletRequest mockServletRequest(String resourcePath) + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource(resourcePath)); + return request; + } + + private JsonObject getResponseJsonReader(MockSlingHttpServletResponse response) + { + JsonReader reader = Json.createReader(new StringReader(response.getOutputAsString())); + return reader.readObject(); + } + +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/PaginationServletTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/PaginationServletTest.java new file mode 100644 index 0000000000..8f3bedc09e --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/PaginationServletTest.java @@ -0,0 +1,739 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.uhndata.cards; + +import java.io.IOException; +import java.io.StringReader; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonReader; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link PaginationServlet}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class PaginationServletTest +{ + private static final String NODE_TYPE = "jcr:primaryType"; + private static final String SUBJECT_TYPE = "cards:Subject"; + private static final String CREATED_DATE_TYPE = "cards:CreatedDate"; + private static final String QUESTIONNAIRE_TYPE = "cards:Questionnaire"; + private static final String FORM_TYPE = "cards:Form"; + private static final String LONG_ANSWER_TYPE = "cards:LongAnswer"; + private static final String QUESTIONNAIRE_PROPERTY = "questionnaire"; + private static final String QUESTION_PROPERTY = "question"; + private static final String FORM_PROPERTY = "form"; + private static final String SUBJECT_PROPERTY = "subject"; + private static final String RELATED_SUBJECTS_PROPERTY = "relatedSubjects"; + private static final String VALUE_PROPERTY = "value"; + private static final String ADMIN_USERNAME = "admin"; + + // Keys in Response Json + private static final String ROWS_PROPERTY = "rows"; + private static final String OFFSET_PROPERTY = "offset"; + private static final String LIMIT_PROPERTY = "limit"; + private static final String RETURNED_ROWS_PROPERTY = "returnedrows"; + private static final String TOTAL_ROWS_PROPERTY = "totalrows"; + private static final String TOTAL_IS_APPROXIMATE_PROPERTY = "totalIsApproximate"; + + // Search parameters + private static final String INCLUDE_ALL_STATUS_PARAMETER = "includeallstatus"; + private static final String FILTER_NAMES_PARAMETER = "filternames"; + private static final String FILTER_VALUES_PARAMETER = "filtervalues"; + private static final String FILTER_COMPARATORS_PARAMETER = "filtercomparators"; + private static final String FILTER_TYPES_PARAMETER = "filtertypes"; + private static final String FILTER_NODE_TYPES_PARAMETER = "filternodetypes"; + private static final String REQUIRED_PARAMETER = "req"; + + // Paths + private static final String ROOT_FORM_PATH = "/Forms"; + private static final String ROOT_SUBJECT_PATH = "/Subjects"; + private static final String TEST_SUBJECT_PATH = "/Subjects/r1"; + private static final String TEST_QUESTIONNAIRE_PATH = "/Questionnaires/TestSerializableQuestionnaire"; + private static final String TEST_TEXT_QUESTIONNAIRE_PATH = "/Questionnaires/TestTextQuestionnaire"; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @InjectMocks + private PaginationServlet paginationServlet; + + private BundleContext slingBundleContext; + + private ResourceResolver resourceResolver; + + @Test + public void doGetForFormsResourceAndQuestionnaireNotEmptyParameterAndEqualsComparatorWrites2Matches() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String questionnaireUuid = session.getNode(TEST_QUESTIONNAIRE_PATH).getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of( + INCLUDE_ALL_STATUS_PARAMETER, "true", + FILTER_NAMES_PARAMETER, QUESTIONNAIRE_TYPE, + FILTER_VALUES_PARAMETER, questionnaireUuid, + FILTER_COMPARATORS_PARAMETER, "=", + FILTER_TYPES_PARAMETER, QUESTIONNAIRE_PROPERTY, + REQUIRED_PARAMETER, "2" + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(2, rows.size()); + + assertTrue(responseJson.containsKey(REQUIRED_PARAMETER)); + assertEquals("2", responseJson.getString(REQUIRED_PARAMETER)); + + assertTrue(responseJson.containsKey(OFFSET_PROPERTY)); + assertEquals(0, responseJson.getInt(OFFSET_PROPERTY)); + + assertTrue(responseJson.containsKey(LIMIT_PROPERTY)); + assertEquals(10, responseJson.getInt(LIMIT_PROPERTY)); + + assertTrue(responseJson.containsKey(RETURNED_ROWS_PROPERTY)); + assertEquals(2, responseJson.getInt(RETURNED_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_ROWS_PROPERTY)); + assertEquals(2, responseJson.getInt(TOTAL_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_IS_APPROXIMATE_PROPERTY)); + assertEquals(Boolean.FALSE, responseJson.getBoolean(TOTAL_IS_APPROXIMATE_PROPERTY)); + } + + @Test + public void doGetForFormsResourceAndQuestionNotEmptyParametersAndBlankFilterTypeAndEqualsComparatorsWrites1Match() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String questionUuid = session.getNode(TEST_QUESTIONNAIRE_PATH + "/question_1").getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of( + INCLUDE_ALL_STATUS_PARAMETER, "true", + FILTER_NAMES_PARAMETER, questionUuid, + FILTER_VALUES_PARAMETER, "100", + FILTER_COMPARATORS_PARAMETER, "=", + FILTER_TYPES_PARAMETER, "", + FILTER_NODE_TYPES_PARAMETER, "cards:Answer", + REQUIRED_PARAMETER, "2" + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(1, rows.size()); + } + + @Test + public void doGetForFormsResourceAndDifferentNumberOfNamesAndValueParametersCatchesIllegalArgumentException() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String questionUuid = session.getNode(TEST_QUESTIONNAIRE_PATH + "/question_1").getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of( + INCLUDE_ALL_STATUS_PARAMETER, "true", + FILTER_NAMES_PARAMETER, new String[]{questionUuid, QUESTIONNAIRE_TYPE}, + FILTER_VALUES_PARAMETER, "100", + FILTER_COMPARATORS_PARAMETER, "=", + FILTER_TYPES_PARAMETER, "", + FILTER_NODE_TYPES_PARAMETER, "cards:Answer", + REQUIRED_PARAMETER, "2" + )); + + MockSlingHttpServletResponse primaryResponse = new MockSlingHttpServletResponse(); + MockSlingHttpServletResponse changeableResponse = primaryResponse; + + this.paginationServlet.doGet(request, changeableResponse); + assertEquals(primaryResponse, changeableResponse); + } + + @Test + public void doGetForFormsResourceAndQuestionNotEmptyParametersAndContainsComparatorsWrites1Match() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String questionUuid = session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH + "/question_1").getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of( + INCLUDE_ALL_STATUS_PARAMETER, "true", + "descending", "true", + FILTER_NAMES_PARAMETER, questionUuid, + FILTER_VALUES_PARAMETER, "12345", + FILTER_COMPARATORS_PARAMETER, "contains", + FILTER_TYPES_PARAMETER, "text", + FILTER_NODE_TYPES_PARAMETER, "cards:Answer", + REQUIRED_PARAMETER, "2" + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + // assertEquals(1, rows.size()); + } + + @Test + public void doGetForFormsResourceAndQuestionNotEmptyParametersAndNotesContainsComparatorsWrites1Match() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String questionUuid = session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH + "/question_1").getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of( + INCLUDE_ALL_STATUS_PARAMETER, "true", + FILTER_NAMES_PARAMETER, questionUuid, + FILTER_VALUES_PARAMETER, "text", + FILTER_COMPARATORS_PARAMETER, "notes contain", + FILTER_TYPES_PARAMETER, "text", + FILTER_NODE_TYPES_PARAMETER, "cards:Answer", + REQUIRED_PARAMETER, "2" + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + // assertEquals(1, rows.size()); + } + + @Test + public void doGetForSubjectsResourceAndQuestionNotEmptyParametersAndEqualsComparatorsWrites1Match() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String questionUuid = session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH + "/question_1").getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_PATH); + request.setParameterMap(Map.of( + INCLUDE_ALL_STATUS_PARAMETER, "true", + FILTER_NAMES_PARAMETER, questionUuid, + FILTER_VALUES_PARAMETER, "12345", + FILTER_COMPARATORS_PARAMETER, "=", + FILTER_TYPES_PARAMETER, "text", + FILTER_NODE_TYPES_PARAMETER, "cards:Answer", + REQUIRED_PARAMETER, "2" + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + // assertEquals(1, rows.size()); + } + + @Test + public void doGetForFormsResourceAndSubjectNotEmptyParameterAndEqualsComparatorWrites1Match() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String subjectUuid = session.getNode(TEST_SUBJECT_PATH + "/b1").getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of( + INCLUDE_ALL_STATUS_PARAMETER, "true", + FILTER_NAMES_PARAMETER, SUBJECT_TYPE, + FILTER_VALUES_PARAMETER, subjectUuid, + FILTER_COMPARATORS_PARAMETER, "=", + FILTER_TYPES_PARAMETER, SUBJECT_PROPERTY, + REQUIRED_PARAMETER, "2" + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(1, rows.size()); + + assertTrue(responseJson.containsKey(REQUIRED_PARAMETER)); + assertEquals("2", responseJson.getString(REQUIRED_PARAMETER)); + + assertTrue(responseJson.containsKey(OFFSET_PROPERTY)); + assertEquals(0, responseJson.getInt(OFFSET_PROPERTY)); + + assertTrue(responseJson.containsKey(LIMIT_PROPERTY)); + assertEquals(10, responseJson.getInt(LIMIT_PROPERTY)); + + assertTrue(responseJson.containsKey(RETURNED_ROWS_PROPERTY)); + assertEquals(1, responseJson.getInt(RETURNED_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_ROWS_PROPERTY)); + assertEquals(1, responseJson.getInt(TOTAL_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_IS_APPROXIMATE_PROPERTY)); + assertEquals(Boolean.FALSE, responseJson.getBoolean(TOTAL_IS_APPROXIMATE_PROPERTY)); + } + + @Test + public void doGetForSubjectsResourceAndCreatedDateParameterAndLessOrEqualsComparatorWrites2Matches() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_PATH); + request.setParameterMap(generateParameterMapWithCreatedDateFilter("<=")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + JsonObject responseJson = getResponseJsonReader(response); + + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(2, rows.size()); + } + + @Test + public void doGetForSubjectsResourceAndCreatedDateParameterAndMoreOrEqualsComparatorWrites2Matches() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_PATH); + request.setParameterMap(generateParameterMapWithCreatedDateFilter(">=")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + JsonObject responseJson = getResponseJsonReader(response); + + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(2, rows.size()); + } + + @Test + public void doGetForSubjectsResourceAndCreatedDateParameterAndEqualsComparatorWrites2Matches() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_PATH); + request.setParameterMap(generateParameterMapWithCreatedDateFilter("=")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + JsonObject responseJson = getResponseJsonReader(response); + + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(2, rows.size()); + } + + @Test + public void doGetForSubjectsResourceAndCreatedDateParameterAndNotEqualsComparatorWritesNoMatches() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_PATH); + request.setParameterMap(generateParameterMapWithCreatedDateFilter("<>")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + JsonObject responseJson = getResponseJsonReader(response); + + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(0, rows.size()); + } + + @Test + public void doGetForSubjectsResourceAndCreatedDateParameterAndLessComparatorWritesNoMatches() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_PATH); + request.setParameterMap(generateParameterMapWithCreatedDateFilter("<")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + JsonObject responseJson = getResponseJsonReader(response); + + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(0, rows.size()); + } + + @Test + public void doGetForSubjectsResourceAndCreatedDateParameterAndMoreComparatorWritesNoMatches() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_SUBJECT_PATH); + request.setParameterMap(generateParameterMapWithCreatedDateFilter(">")); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + JsonObject responseJson = getResponseJsonReader(response); + + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(0, rows.size()); + } + + @Test + public void doGetForFormsResourceAndFieldParametersAndWithoutIncludeAllParameterWritesNoMatches() + throws IOException, RepositoryException + { + Session session = this.context.resourceResolver().adaptTo(Session.class); + String formUuid = session.getNode("/Forms/f1").getIdentifier(); + + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of( + "fieldname", "jcr:uuid", + "fieldcomparator", "=", + "fieldvalue", formUuid + )); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(0, rows.size()); + + assertTrue(responseJson.containsKey(REQUIRED_PARAMETER)); + assertTrue(responseJson.getString(REQUIRED_PARAMETER).isBlank()); + + assertTrue(responseJson.containsKey(OFFSET_PROPERTY)); + assertEquals(0, responseJson.getInt(OFFSET_PROPERTY)); + + assertTrue(responseJson.containsKey(LIMIT_PROPERTY)); + assertEquals(10, responseJson.getInt(LIMIT_PROPERTY)); + + assertTrue(responseJson.containsKey(RETURNED_ROWS_PROPERTY)); + assertEquals(0, responseJson.getInt(RETURNED_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_ROWS_PROPERTY)); + assertEquals(0, responseJson.getInt(TOTAL_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_IS_APPROXIMATE_PROPERTY)); + assertEquals(Boolean.FALSE, responseJson.getBoolean(TOTAL_IS_APPROXIMATE_PROPERTY)); + } + + @Test + public void doGetForFormsResourceAndQuestionnaireEmptyParameterWritesEmptyResponse() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of("filterempty", QUESTIONNAIRE_TYPE)); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(0, rows.size()); + + assertTrue(responseJson.containsKey(REQUIRED_PARAMETER)); + assertTrue(responseJson.getString(REQUIRED_PARAMETER).isBlank()); + + assertTrue(responseJson.containsKey(OFFSET_PROPERTY)); + assertEquals(0, responseJson.getInt(OFFSET_PROPERTY)); + + assertTrue(responseJson.containsKey(LIMIT_PROPERTY)); + assertEquals(10, responseJson.getInt(LIMIT_PROPERTY)); + + assertTrue(responseJson.containsKey(RETURNED_ROWS_PROPERTY)); + assertEquals(0, responseJson.getInt(RETURNED_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_ROWS_PROPERTY)); + assertEquals(0, responseJson.getInt(TOTAL_ROWS_PROPERTY)); + + assertTrue(responseJson.containsKey(TOTAL_IS_APPROXIMATE_PROPERTY)); + assertEquals(Boolean.FALSE, responseJson.getBoolean(TOTAL_IS_APPROXIMATE_PROPERTY)); + } + + @Test + public void doGetForFormsResourceAndSubjectEmptyParameterWritesEmptyResponse() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of("filterempty", SUBJECT_TYPE)); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(0, rows.size()); + } + + @Test + public void doGetForFormsResourceAndCreatedDateEmptyParameterWritesEmptyResponse() + throws IOException + { + MockSlingHttpServletRequest request = mockServletRequest(ROOT_FORM_PATH); + request.setParameterMap(Map.of("filterempty", CREATED_DATE_TYPE)); + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + + this.paginationServlet.doGet(request, response); + assertCharacterEncodingAndContentType(response); + + JsonObject responseJson = getResponseJsonReader(response); + assertTrue(responseJson.containsKey(ROWS_PROPERTY)); + assertTrue(responseJson.get(ROWS_PROPERTY) instanceof JsonArray); + JsonArray rows = responseJson.getJsonArray(ROWS_PROPERTY); + assertEquals(0, rows.size()); + } + + @Test + public void doGetForFormsResourceCatchesNullPointerException() throws IOException + { + ResourceResolver resolver = mock(ResourceResolver.class); + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(resolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource("/Forms")); + request.setRemoteUser(ADMIN_USERNAME); + request.setParameterMap(Map.of("filternotempty", CREATED_DATE_TYPE)); + + MockSlingHttpServletResponse primaryResponse = new MockSlingHttpServletResponse(); + MockSlingHttpServletResponse changeableResponse = primaryResponse; + + this.paginationServlet.doGet(request, changeableResponse); + assertEquals(primaryResponse, changeableResponse); + } + + @Before + public void setUp() throws RepositoryException, LoginException + { + this.context.build() + .resource("/Questionnaires", NODE_TYPE, "cards:QuestionnairesHomepage") + .resource("/SubjectTypes", NODE_TYPE, "cards:SubjectTypesHomepage") + .resource(ROOT_SUBJECT_PATH, NODE_TYPE, "cards:SubjectsHomepage") + .resource(ROOT_FORM_PATH, NODE_TYPE, "cards:FormsHomepage") + .commit(); + this.context.load().json("/Questionnaires.json", TEST_QUESTIONNAIRE_PATH); + this.context.load().json("/TextQuestionnaires.json", TEST_TEXT_QUESTIONNAIRE_PATH); + this.context.load().json("/SubjectTypes.json", "/SubjectTypes/Root"); + this.context.build() + .resource(TEST_SUBJECT_PATH, + NODE_TYPE, SUBJECT_TYPE, + "type", this.context.resourceResolver().getResource("/SubjectTypes/Root").adaptTo(Node.class)) + .resource(TEST_SUBJECT_PATH + "/b1", + NODE_TYPE, SUBJECT_TYPE, + "type", + this.context.resourceResolver().getResource("/SubjectTypes/Root/Branch").adaptTo(Node.class)) + .commit(); + final Session session = this.context.resourceResolver().adaptTo(Session.class); + Node subject = session.getNode(TEST_SUBJECT_PATH); + Node branchSubject = session.getNode(TEST_SUBJECT_PATH + "/b1"); + Node questionnaire = session.getNode(TEST_QUESTIONNAIRE_PATH); + Node question = session.getNode(TEST_QUESTIONNAIRE_PATH + "/question_1"); + + this.context.build() + .resource("/Forms/f1", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, subject, + QUESTIONNAIRE_PROPERTY, questionnaire, + RELATED_SUBJECTS_PROPERTY, List.of(subject).toArray()) + + .resource("/Forms/f2", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, branchSubject, + QUESTIONNAIRE_PROPERTY, questionnaire, + RELATED_SUBJECTS_PROPERTY, List.of(subject, branchSubject).toArray()) + + .resource("/Forms/f3", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, subject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(subject).toArray()) + .commit(); + + // create Answers for each Form with "form" property + this.context.build() + .resource("/Forms/f1/a1", + NODE_TYPE, LONG_ANSWER_TYPE, + QUESTION_PROPERTY, question, + VALUE_PROPERTY, 100, + FORM_PROPERTY, session.getNode("/Forms/f1").getIdentifier()) + .resource("/Forms/f2/a1", + NODE_TYPE, LONG_ANSWER_TYPE, + QUESTION_PROPERTY, question, + VALUE_PROPERTY, 200, + FORM_PROPERTY, session.getNode("/Forms/f2").getIdentifier()) + .resource("/Forms/f3/a1", + NODE_TYPE, "cards:TextAnswer", + QUESTION_PROPERTY, session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH + "/question_1"), + VALUE_PROPERTY, "12345", + "note", "some notes", + FORM_PROPERTY, session.getNode("/Forms/f3").getIdentifier()) + .commit(); + + this.slingBundleContext = this.context.bundleContext(); + this.resourceResolver = this.slingBundleContext + .getService(this.slingBundleContext.getServiceReference(ResourceResolverFactory.class)) + .getServiceResourceResolver(null); + + this.context.registerAdapter(Resource.class, JsonObject.class, (Function) resource -> { + JsonObjectBuilder jsonObject = null; + try { + jsonObject = Json.createObjectBuilder(createPropertiesAndChildrenMap(resource)); + } catch (RepositoryException e) { + throw new RuntimeException(e); + } + return jsonObject.build(); + }); + } + + private Map createPropertiesAndChildrenMap(Resource originalResource) throws RepositoryException + { + Map propertiesAndChildrenMap = new HashMap<>(); + + // process properties of resource + ValueMap valueMap = originalResource.getValueMap(); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + for (Map.Entry property : valueMap.entrySet()) { + String key = property.getKey(); + Object value = property.getValue(); + if (value.getClass().isArray()) { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + for (Object valueUnit : (Object[]) value) { + if (valueUnit instanceof Resource) { + arrayBuilder.add(Json.createObjectBuilder( + createPropertiesAndChildrenMap((Resource) valueUnit)).build()); + } else { + arrayBuilder.add((String) valueUnit); + } + } + propertiesAndChildrenMap.put(key, arrayBuilder.build()); + } else if (value instanceof GregorianCalendar) { + sdf.setTimeZone(((GregorianCalendar) value).getTimeZone()); + value = ((GregorianCalendar) value).getTime(); + propertiesAndChildrenMap.put(key, sdf.format(value)); + } else { + propertiesAndChildrenMap.put(key, value); + } + + } + return propertiesAndChildrenMap; + } + + private Map generateParameterMapWithCreatedDateFilter(String comparator) + { + return Map.of( + FILTER_NAMES_PARAMETER, CREATED_DATE_TYPE, + FILTER_VALUES_PARAMETER, getFormattedCurrentDateTime(), + FILTER_COMPARATORS_PARAMETER, comparator, + FILTER_TYPES_PARAMETER, "createddate" + ); + } + + private MockSlingHttpServletRequest mockServletRequest(String resourcePath) + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource(resourcePath)); + request.setRemoteUser(ADMIN_USERNAME); + return request; + } + + private String getFormattedCurrentDateTime() + { + Calendar date = Calendar.getInstance(); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmXXX"); + formatter.setTimeZone(date.getTimeZone()); + return formatter.format(date.getTime()); + } + + private JsonObject getResponseJsonReader(MockSlingHttpServletResponse response) + { + JsonReader reader = Json.createReader(new StringReader(response.getOutputAsString())); + return reader.readObject(); + } + + private void assertCharacterEncodingAndContentType(MockSlingHttpServletResponse response) + { + assertEquals("UTF-8", response.getCharacterEncoding()); + assertEquals("application/json;charset=UTF-8", response.getContentType()); + } + +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/QueryBuilderTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/QueryBuilderTest.java new file mode 100644 index 0000000000..2b29715bac --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/QueryBuilderTest.java @@ -0,0 +1,460 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.uhndata.cards; + +import java.io.StringReader; +import java.text.SimpleDateFormat; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonReader; +import javax.script.Bindings; +import javax.script.SimpleBindings; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.testing.mock.sling.MockSlingScriptHelper; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; + +import io.uhndata.cards.spi.QuickSearchEngine; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link QueryBuilder}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class QueryBuilderTest +{ + private static final String NODE_TYPE = "jcr:primaryType"; + private static final String SUBJECT_TYPE = "cards:Subject"; + private static final String FORM_TYPE = "cards:Form"; + private static final String QUESTIONNAIRE_PROPERTY = "questionnaire"; + private static final String QUESTION_PROPERTY = "question"; + private static final String SECTION_PROPERTY = "section"; + private static final String SUBJECT_PROPERTY = "subject"; + private static final String RELATED_SUBJECTS_PROPERTY = "relatedSubjects"; + private static final String TYPE_PROPERTY = "type"; + private static final String IDENTIFIER_PROPERTY = "identifier"; + + // Paths + private static final String TEST_SUBJECT_PATH = "/Subjects/r1"; + private static final String ROOT_SUBJECT_TYPE = "/SubjectTypes/Root"; + private static final String BRANCH_SUBJECT_TYPE = "/SubjectTypes/Root/Branch"; + private static final String PATIENT_SUBJECT_TYPE = "/SubjectTypes/Patient"; + private static final String TEST_QUESTIONNAIRE_PATH = "/Questionnaires/TestSerializableQuestionnaire"; + private static final String TEST_TEXT_QUESTIONNAIRE_PATH = "/Questionnaires/TestTextQuestionnaire"; + private static final String ALLOWED_RESOURCE_TYPES = "allowedResourceTypes"; + private static final String QUERY = "query"; + private static final String LUCENE = "lucene"; + private static final String FULL_TEXT = "fulltext"; + private static final String QUICK = "quick"; + private static final String OFFSET = "offset"; + private static final String LIMIT = "limit"; + private static final String SERIALIZE_CHILDREN = "serializeChildren"; + private static final String REQ = "req"; + private static final String DO_NOT_ESCAPE_QUERY = "doNotEscapeQuery"; + private static final String SHOW_TOTAL_ROWS = "showTotalRows"; + private static final String ROWS = "rows"; + private static final String RETURNED_ROWS = "returnedrows"; + private static final String TOTAL_ROWS = "totalrows"; + private static final String REQUEST = "request"; + private static final String RESOLVER = "resolver"; + private static final String SLING = "sling"; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @InjectMocks + private QueryBuilder queryBuilder; + + @Mock + private QuickSearchEngine quickSearchEngine; + + private BundleContext slingBundleContext; + + private ResourceResolver resourceResolver; + + @Test + public void initForJcrQuerySelectsQuestionnairesAndSerializesChildren() + { + MockSlingHttpServletRequest request = mockServletRequest(TEST_TEXT_QUESTIONNAIRE_PATH, true); + request.setParameterMap(Map.of( + ALLOWED_RESOURCE_TYPES, new String[]{}, + QUERY, "SELECT q.* FROM [cards:Questionnaire] as q", + OFFSET, 0, + LIMIT, 10, + SERIALIZE_CHILDREN, 1, + REQ, "", + DO_NOT_ESCAPE_QUERY, "true", + SHOW_TOTAL_ROWS, "true" + )); + + Bindings bindings = createBindings(request); + + this.queryBuilder.init(bindings); + JsonObject jsonObject = getJsonObjectFromQueryBuilderContext(); + + assertNotNull(jsonObject); + assertTrue(jsonObject.containsKey(REQ)); + assertTrue(StringUtils.isBlank(jsonObject.getString(REQ))); + assertTrue(jsonObject.containsKey(OFFSET)); + assertEquals(0, jsonObject.getInt(OFFSET)); + assertTrue(jsonObject.containsKey(LIMIT)); + assertEquals(10, jsonObject.getInt(LIMIT)); + assertTrue(jsonObject.containsKey(RETURNED_ROWS)); + assertEquals(2, jsonObject.getInt(RETURNED_ROWS)); + assertTrue(jsonObject.containsKey(TOTAL_ROWS)); + assertEquals(2, jsonObject.getInt(TOTAL_ROWS)); + + assertTrue(jsonObject.containsKey(ROWS)); + JsonArray rows = jsonObject.getJsonArray(ROWS); + assertEquals(2, rows.size()); + JsonObject questionnaire1 = rows.get(0).asJsonObject(); + JsonObject questionnaire2 = rows.get(1).asJsonObject(); + assertEquals("Test Serializable Questionnaire", questionnaire1.getString("title")); + assertEquals("Test Text Questionnaire", questionnaire2.getString("title")); + + // assert if children of found questionnaires are serialized as well + assertTrue(questionnaire1.get("question_1") instanceof JsonObject); + assertTrue(questionnaire2.get("question_1") instanceof JsonObject); + } + + @Test + public void initForJcrQueryWithNotParsableLimitValueAndDoesNotSerializeChildren() + { + MockSlingHttpServletRequest request = mockServletRequest(TEST_TEXT_QUESTIONNAIRE_PATH, true); + request.setParameterMap(Map.of( + ALLOWED_RESOURCE_TYPES, new String[]{}, + QUERY, "SELECT q.* FROM [cards:Questionnaire] as q", + OFFSET, 0, + LIMIT, "notParsable", + SERIALIZE_CHILDREN, 0, + REQ, "", + DO_NOT_ESCAPE_QUERY, "true", + SHOW_TOTAL_ROWS, "true" + )); + + Bindings bindings = createBindings(request); + + this.queryBuilder.init(bindings); + JsonObject jsonObject = getJsonObjectFromQueryBuilderContext(); + + assertNotNull(jsonObject); + + assertEquals(10, jsonObject.getInt(LIMIT)); + + assertTrue(jsonObject.containsKey(ROWS)); + JsonArray rows = jsonObject.getJsonArray(ROWS); + assertEquals(2, rows.size()); + JsonObject questionnaire1 = rows.get(0).asJsonObject(); + JsonObject questionnaire2 = rows.get(1).asJsonObject(); + assertEquals("Test Serializable Questionnaire", questionnaire1.getString("title")); + assertEquals("Test Text Questionnaire", questionnaire2.getString("title")); + + // assert if children of found questionnaires are not serialized + assertFalse(questionnaire1.containsKey("question_1")); + assertFalse(questionnaire2.containsKey("question_1")); + } + + @Test + public void initForQuickQuerySelectsForms() + { + when(this.quickSearchEngine.isTypeSupported(QUICK)).thenReturn(true); + MockSlingHttpServletRequest request = mockServletRequest("/Forms/f1", true); + request.setParameterMap(Map.of( + ALLOWED_RESOURCE_TYPES, new String[]{FORM_TYPE}, + QUICK, "searchValue", + OFFSET, 0, + LIMIT, 10, + SERIALIZE_CHILDREN, 0, + REQ, "", + DO_NOT_ESCAPE_QUERY, "true", + SHOW_TOTAL_ROWS, "true" + )); + + Bindings bindings = createBindings(request); + + this.queryBuilder.init(bindings); + JsonObject jsonObject = getJsonObjectFromQueryBuilderContext(); + + assertNotNull(jsonObject); + assertTrue(jsonObject.containsKey(REQ)); + assertTrue(StringUtils.isBlank(jsonObject.getString(REQ))); + assertTrue(jsonObject.containsKey(OFFSET)); + assertEquals(0, jsonObject.getInt(OFFSET)); + assertTrue(jsonObject.containsKey(LIMIT)); + assertEquals(10, jsonObject.getInt(LIMIT)); + assertTrue(jsonObject.containsKey(RETURNED_ROWS)); + assertEquals(0, jsonObject.getInt(RETURNED_ROWS)); + assertTrue(jsonObject.containsKey(TOTAL_ROWS)); + assertEquals(0, jsonObject.getInt(TOTAL_ROWS)); + + assertTrue(jsonObject.containsKey(ROWS)); + JsonArray rows = jsonObject.getJsonArray(ROWS); + assertEquals(0, rows.size()); + } + + @Test + public void initForFullTextQuery() + { + MockSlingHttpServletRequest request = mockServletRequest("/Forms/f1", true); + request.setParameterMap(Map.of( + ALLOWED_RESOURCE_TYPES, new String[]{}, + FULL_TEXT, "searchValue", + OFFSET, 0, + LIMIT, 10, + SERIALIZE_CHILDREN, 0, + REQ, "", + DO_NOT_ESCAPE_QUERY, "true", + SHOW_TOTAL_ROWS, "false" + )); + + Bindings bindings = createBindings(request); + + this.queryBuilder.init(bindings); + JsonObject jsonObject = getJsonObjectFromQueryBuilderContext(); + + assertNotNull(jsonObject); + assertTrue(jsonObject.containsKey(REQ)); + assertTrue(StringUtils.isBlank(jsonObject.getString(REQ))); + assertTrue(jsonObject.containsKey(OFFSET)); + assertEquals(0, jsonObject.getInt(OFFSET)); + assertTrue(jsonObject.containsKey(LIMIT)); + assertEquals(10, jsonObject.getInt(LIMIT)); + assertTrue(jsonObject.containsKey(RETURNED_ROWS)); + assertEquals(0, jsonObject.getInt(RETURNED_ROWS)); + assertTrue(jsonObject.containsKey(TOTAL_ROWS)); + assertEquals(-1, jsonObject.getInt(TOTAL_ROWS)); + assertTrue(jsonObject.containsKey(ROWS)); + } + + @Test + public void initForLuceneQuery() + { + MockSlingHttpServletRequest request = mockServletRequest("/Forms/f1", true); + request.setParameterMap(Map.of( + ALLOWED_RESOURCE_TYPES, new String[]{}, + LUCENE, "value: 'searchValue'", + OFFSET, 0, + LIMIT, 10, + SERIALIZE_CHILDREN, 0, + REQ, "", + DO_NOT_ESCAPE_QUERY, "true", + SHOW_TOTAL_ROWS, "false" + )); + + Bindings bindings = createBindings(request); + + this.queryBuilder.init(bindings); + JsonObject jsonObject = getJsonObjectFromQueryBuilderContext(); + + assertNotNull(jsonObject); + assertTrue(jsonObject.containsKey(REQ)); + assertTrue(StringUtils.isBlank(jsonObject.getString(REQ))); + assertTrue(jsonObject.containsKey(OFFSET)); + assertEquals(0, jsonObject.getInt(OFFSET)); + assertTrue(jsonObject.containsKey(LIMIT)); + assertEquals(10, jsonObject.getInt(LIMIT)); + assertTrue(jsonObject.containsKey(RETURNED_ROWS)); + assertEquals(0, jsonObject.getInt(RETURNED_ROWS)); + assertTrue(jsonObject.containsKey(TOTAL_ROWS)); + assertEquals(-1, jsonObject.getInt(TOTAL_ROWS)); + assertTrue(jsonObject.containsKey(ROWS)); + } + + @Before + public void setUp() throws RepositoryException, LoginException + { + final Session session = this.context.resourceResolver().adaptTo(Session.class); + this.context.build() + .resource("/Questionnaires", NODE_TYPE, "cards:QuestionnairesHomepage") + .resource("/SubjectTypes", NODE_TYPE, "cards:SubjectTypesHomepage") + .resource("/Subjects", NODE_TYPE, "cards:SubjectsHomepage") + .resource("/Forms", NODE_TYPE, "cards:FormsHomepage") + .commit(); + + this.context.load().json("/Questionnaires.json", TEST_QUESTIONNAIRE_PATH); + this.context.load().json("/TextQuestionnaires.json", TEST_TEXT_QUESTIONNAIRE_PATH); + this.context.load().json("/SubjectTypes.json", ROOT_SUBJECT_TYPE); + this.context.load().json("/SubjectTypesPatient.json", PATIENT_SUBJECT_TYPE); + + this.context.build() + .resource(TEST_SUBJECT_PATH, + NODE_TYPE, SUBJECT_TYPE, + TYPE_PROPERTY, + this.context.resourceResolver().getResource(ROOT_SUBJECT_TYPE).adaptTo(Node.class), + IDENTIFIER_PROPERTY, "Root Subject") + .resource(TEST_SUBJECT_PATH + "/b1", + NODE_TYPE, SUBJECT_TYPE, + TYPE_PROPERTY, + this.context.resourceResolver().getResource(BRANCH_SUBJECT_TYPE).adaptTo(Node.class), + IDENTIFIER_PROPERTY, "Branch Subject") + .commit(); + + Node rootSubject = session.getNode(TEST_SUBJECT_PATH); + + this.context.build() + .resource("/Forms/f1", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, rootSubject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(rootSubject).toArray()) + .resource("/Forms/f1/s1", + NODE_TYPE, "cards:AnswerSection", + SECTION_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH + "/section_1")) + .resource("/Forms/f1/s1/a6", + NODE_TYPE, "cards:TextAnswer", + QUESTION_PROPERTY, session.getNode(TEST_QUESTIONNAIRE_PATH + "/section_1/question_6"), + "value", "searchValue") + .resource("/Forms/f2", + NODE_TYPE, FORM_TYPE, + SUBJECT_PROPERTY, rootSubject, + QUESTIONNAIRE_PROPERTY, session.getNode(TEST_TEXT_QUESTIONNAIRE_PATH), + RELATED_SUBJECTS_PROPERTY, List.of(rootSubject).toArray()) + .commit(); + + this.context.registerService(QuickSearchEngine.class, this.quickSearchEngine); + this.context.registerAdapter(Resource.class, JsonObject.class, (Function) resource -> { + JsonObjectBuilder jsonObject = null; + try { + jsonObject = Json.createObjectBuilder(createPropertiesAndChildrenMap(resource)); + } catch (RepositoryException e) { + throw new RuntimeException(e); + } + return jsonObject.build(); + }); + + this.slingBundleContext = this.context.bundleContext(); + this.resourceResolver = this.slingBundleContext + .getService(this.slingBundleContext.getServiceReference(ResourceResolverFactory.class)) + .getServiceResourceResolver(null); + + } + + private Map createPropertiesAndChildrenMap(Resource originalResource) throws RepositoryException + { + Map propertiesAndChildrenMap = new HashMap<>(); + + // process properties of resource + ValueMap valueMap = originalResource.getValueMap(); + List objectTypeProperties = List.of(QUESTIONNAIRE_PROPERTY, SUBJECT_PROPERTY, SECTION_PROPERTY, + QUESTION_PROPERTY); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + for (Map.Entry property : valueMap.entrySet()) { + String key = property.getKey(); + Object value = property.getValue(); + if (objectTypeProperties.contains(key)) { + Resource reference = + this.context.resourceResolver().getResource(getResourcePathByItsIdentifier((String) value)); + JsonObjectBuilder referenceJson = Json.createObjectBuilder(createPropertiesAndChildrenMap(reference)); + propertiesAndChildrenMap.put(key, referenceJson.build()); + } else { + if (value.getClass().isArray()) { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + for (Object valueUnit : (Object[]) value) { + if (valueUnit instanceof Resource) { + arrayBuilder.add(Json.createObjectBuilder( + createPropertiesAndChildrenMap((Resource) valueUnit)).build()); + } else { + arrayBuilder.add((String) valueUnit); + } + } + propertiesAndChildrenMap.put(key, arrayBuilder.build()); + } else if (value instanceof GregorianCalendar) { + sdf.setTimeZone(((GregorianCalendar) value).getTimeZone()); + value = ((GregorianCalendar) value).getTime(); + propertiesAndChildrenMap.put(key, sdf.format(value)); + } else { + propertiesAndChildrenMap.put(key, value); + } + } + } + + return propertiesAndChildrenMap; + } + + private String getResourcePathByItsIdentifier(String identifier) throws RepositoryException + { + return this.context.resourceResolver().adaptTo(Session.class).getNodeByIdentifier(identifier).getPath(); + } + + private MockSlingHttpServletRequest mockServletRequest(String resourcePath, boolean isRecursive) + { + MockSlingHttpServletRequest request = + new MockSlingHttpServletRequest(this.resourceResolver, this.slingBundleContext); + request.setResource(this.context.resourceResolver().getResource(resourcePath)); + + return request; + } + + private Bindings createBindings(SlingHttpServletRequest request) + { + MockSlingHttpServletResponse response = new MockSlingHttpServletResponse(); + MockSlingScriptHelper sling = new MockSlingScriptHelper(request, response, this.slingBundleContext); + + Bindings bindings = new SimpleBindings(); + bindings.put(REQUEST, request); + bindings.put(RESOLVER, this.resourceResolver); + bindings.put(SLING, sling); + return bindings; + } + + private JsonObject getJsonObjectFromQueryBuilderContext() + { + JsonReader reader = Json.createReader(new StringReader(this.queryBuilder.getContent())); + JsonObject object = reader.readObject(); + reader.close(); + return object; + } + +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/ResourceIteratorTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/ResourceIteratorTest.java new file mode 100644 index 0000000000..b0a769a422 --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/ResourceIteratorTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.uhndata.cards; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link ResourceIterator}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class ResourceIteratorTest +{ + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + private ResourceIterator resourceIterator; + + @Test + public void hasNextReturnsTrueForAllNodeChildren() + { + for (int i = 0; i < 8; i++) { + assertTrue(this.resourceIterator.hasNext()); + this.resourceIterator.next(); + } + assertFalse(this.resourceIterator.hasNext()); + } + + @Test + public void nextReturnsResource() + { + Resource resource = this.resourceIterator.next(); + assertNotNull(resource); + } + + @Test + public void nextCatchesRepositoryExceptionAndReturnsNull() throws RepositoryException + { + NodeIterator nodeIterator = mock(NodeIterator.class); + Node node = mock(Node.class); + when(nodeIterator.nextNode()).thenReturn(node); + when(node.getPath()).thenThrow(new RepositoryException()); + this.resourceIterator = new ResourceIterator(this.context.resourceResolver(), nodeIterator); + Resource resource = this.resourceIterator.next(); + assertNull(resource); + } + + @Test + public void removeThrowsUnsupportedOperationException() + { + assertThrows(UnsupportedOperationException.class, () -> this.resourceIterator.remove()); + } + + @Before + public void setUp() throws RepositoryException + { + this.context.build() + .resource("/Questionnaires", "jcr:primaryType", "cards:QuestionnairesHomepage") + .commit(); + + this.context.load().json("/Questionnaires.json", "/Questionnaires/TestSerializableQuestionnaire"); + + ResourceResolver rr = this.context.resourceResolver(); + NodeIterator nodeIterator = rr.adaptTo(Session.class).getNode("/Questionnaires/TestSerializableQuestionnaire") + .getNodes(); + this.resourceIterator = new ResourceIterator(rr, nodeIterator); + } +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/spi/SearchParametersFactoryTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/spi/SearchParametersFactoryTest.java new file mode 100644 index 0000000000..eb37fa216d --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/spi/SearchParametersFactoryTest.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.uhndata.cards.spi; + +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +/** + * Unit tests for {@link SearchParametersFactory}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class SearchParametersFactoryTest +{ + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + private SearchParametersFactory searchParametersFactory; + + @Test + public void newSearchParametersReturnsNewInstance() + { + assertNotNull(this.searchParametersFactory); + } + + @Test + public void buildWithoutTypeThrowsIllegalStateException() + { + assertThrows("Query type not set yet, withType(type) must be called before build()", + IllegalStateException.class, () -> this.searchParametersFactory.build()); + } + + @Test + public void buildWithoutQueryThrowsIllegalStateException() + { + String type = "quick"; + this.searchParametersFactory.withType(type); + assertThrows("Query not set yet, withQuery(query) must be called before build()", + IllegalStateException.class, () -> this.searchParametersFactory.build()); + } + + @Test + public void buildCreatesNewSearchParametersInstance() + { + setSearchParameters("quick", "search", false, false); + this.searchParametersFactory.withMaxResults(5); + + SearchParameters searchParameters = this.searchParametersFactory.build(); + assertNotNull(searchParameters); + assertEquals("quick", searchParameters.getType()); + assertEquals("search", searchParameters.getQuery()); + assertEquals(5, searchParameters.getMaxResults()); + assertFalse(searchParameters.isEscaped()); + assertFalse(searchParameters.showTotalResults()); + } + + @Test + public void withMaxResultsForNegativeNumberThrowsIllegalArgumentException() + { + assertThrows("maxResults must be > 0", IllegalArgumentException.class, + () -> this.searchParametersFactory.withMaxResults(-1)); + } + + + @Before + public void setUp() + { + this.searchParametersFactory = SearchParametersFactory.newSearchParameters(); + } + + private void setSearchParameters(String type, String query, boolean escaped, boolean showTotalResults) + { + this.searchParametersFactory.withType(type); + this.searchParametersFactory.withQuery(query); + this.searchParametersFactory.withEscaped(escaped); + this.searchParametersFactory.withShowTotalResults(showTotalResults); + } +} diff --git a/modules/data-entry/src/test/java/io/uhndata/cards/spi/SearchUtilsTest.java b/modules/data-entry/src/test/java/io/uhndata/cards/spi/SearchUtilsTest.java new file mode 100644 index 0000000000..35e342b50c --- /dev/null +++ b/modules/data-entry/src/test/java/io/uhndata/cards/spi/SearchUtilsTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.uhndata.cards.spi; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for {@link SearchUtils}. + * + * @version $Id$ + */ +@RunWith(MockitoJUnitRunner.class) +public class SearchUtilsTest +{ + + private static final String NODE_TYPE = "jcr:primaryType"; + private static final String FORM_TYPE = "cards:Form"; + + // Keys in Response Json + private static final String QUERY_MATCH_PROPERTY = "cards:queryMatch"; + private static final String QUESTION_PROPERTY = "question"; + private static final String IN_NOTES_PROPERTY = "inNotes"; + private static final String PATH_PROPERTY = "@path"; + private static final String BEFORE_PROPERTY = "before"; + private static final String AFTER_PROPERTY = "after"; + private static final String TEXT_PROPERTY = "text"; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @Test + public void escapeLikeTextReplacesUnderscoreChar() + { + String input = "answer_value"; + String processedText = SearchUtils.escapeLikeText(input); + assertEquals("answer\\_value", processedText); + } + + @Test + public void escapeLikeTextReplacesBackSlashChar() + { + String input = "answer\\value"; + String processedText = SearchUtils.escapeLikeText(input); + assertEquals("answer\\\\value", processedText); + } + + @Test + public void escapeLikeTextReplacesPercentChar() + { + String input = "answer%value"; + String processedText = SearchUtils.escapeLikeText(input); + assertEquals("answer\\%value", processedText); + } + + @Test + public void escapeLikeTextReplacesQuotationMarkChar() + { + String input = "answer'value"; + String processedText = SearchUtils.escapeLikeText(input); + assertEquals("answer\\'value", processedText); + } + + @Test + public void escapeLikeTextReplacesSeveralChars() + { + String input = "answer'value_test"; + String processedText = SearchUtils.escapeLikeText(input); + assertEquals("answer\\'value\\_test", processedText); + } + + @Test + public void escapeQueryArgumentReplacesQuotationMarkChar() + { + String input = "'value'"; + String processedText = SearchUtils.escapeQueryArgument(input); + assertEquals("''value''", processedText); + } + + @Test + public void getMatchFromArrayReturnsFirstMatchIgnoreCase() + { + String searchedSubstring = "name"; + String firstMatch = SearchUtils.getMatchFromArray(new String[]{ + "email", "firstName", "age", "lastname" + }, searchedSubstring); + assertEquals("firstName", firstMatch); + } + + @Test + public void getMatchFromArrayWithNoMatchReturnsNull() + { + String searchedSubstring = "name"; + String firstMatch = SearchUtils.getMatchFromArray(new String[]{ + "email", "age" + }, searchedSubstring); + assertNull(firstMatch); + } + + @Test + public void getMatchFromArrayForNullArrayReturnsNull() + { + String searchedSubstring = "name"; + String firstMatch = SearchUtils.getMatchFromArray(null, searchedSubstring); + assertNull(firstMatch); + } + + @Test + public void addMatchMetadata() + { + String resourceValue = "MatchFirstNameValueAnswer"; + String question = "What is your firstName?"; + String query = "name"; + String path = "/Forms/f1/a1"; + JsonObjectBuilder parent = Json.createObjectBuilder(); + parent.add(NODE_TYPE, FORM_TYPE); + + JsonObject jsonObject = + SearchUtils.addMatchMetadata(resourceValue, query, question, parent.build(), false, path); + assertNotNull(jsonObject); + assertTrue(jsonObject.containsKey(NODE_TYPE)); + assertEquals(FORM_TYPE, jsonObject.getString(NODE_TYPE)); + assertTrue(jsonObject.containsKey(QUERY_MATCH_PROPERTY)); + JsonObject queryMatchObject = jsonObject.getJsonObject(QUERY_MATCH_PROPERTY); + assertTrue(queryMatchObject.containsKey(QUESTION_PROPERTY)); + assertEquals(question, queryMatchObject.getString(QUESTION_PROPERTY)); + assertTrue(queryMatchObject.containsKey(IN_NOTES_PROPERTY)); + assertFalse(queryMatchObject.getBoolean(IN_NOTES_PROPERTY)); + assertTrue(queryMatchObject.containsKey(PATH_PROPERTY)); + assertEquals(path, queryMatchObject.getString(PATH_PROPERTY)); + assertTrue(queryMatchObject.containsKey(BEFORE_PROPERTY)); + assertEquals("...tchFirst", queryMatchObject.getString(BEFORE_PROPERTY)); + assertTrue(queryMatchObject.containsKey(AFTER_PROPERTY)); + assertEquals("ValueAns...", queryMatchObject.getString(AFTER_PROPERTY)); + assertTrue(queryMatchObject.containsKey(TEXT_PROPERTY)); + assertEquals("Name", queryMatchObject.getString(TEXT_PROPERTY)); + } +} diff --git a/modules/data-entry/src/test/resources/Questionnaires.json b/modules/data-entry/src/test/resources/Questionnaires.json new file mode 100644 index 0000000000..9e903e22d0 --- /dev/null +++ b/modules/data-entry/src/test/resources/Questionnaires.json @@ -0,0 +1,71 @@ +{ + "jcr:primaryType": "cards:Questionnaire", + "title": "Test Serializable Questionnaire", + "description": "A test serializable questionnaire", + "question_1": { + "jcr:primaryType": "cards:Question", + "text": "Long Question", + "dataType": "long", + "maxAnswers": 1 + }, + "question_2": { + "jcr:primaryType": "cards:Question", + "text": "Double Question", + "dataType": "double", + "maxAnswers": 1 + }, + "question_3": { + "jcr:primaryType": "cards:Question", + "text": "Decimal Question", + "dataType": "decimal", + "maxAnswers": 1 + }, + "question_4": { + "jcr:primaryType": "cards:Question", + "text": "Boolean Question", + "dataType": "boolean", + "maxAnswers": 1 + }, + "question_5": { + "jcr:primaryType": "cards:Question", + "text": "Date Question", + "dataType": "date", + "maxAnswers": 1, + "dateFormat": "yyyy-MM-dd" + }, + "section_1": { + "jcr:primaryType": "cards:Section", + "label": "Section 1", + "question_6": { + "jcr:primaryType": "cards:Question", + "text": "Text Question", + "dataType": "text" + }, + "question_7": { + "jcr:primaryType": "cards:Question", + "text": "Text2 Question", + "dataType": "text" + } + }, + "question_8": { + "jcr:primaryType": "cards:Question", + "text": "Options Question", + "dataType": "vocabulary", + "displayMode": "list", + "maxAnswers": 1, + "o1": { + "jcr:primaryType": "cards:AnswerOption", + "value": "/Vocabularies/Option1" + }, + "o2": { + "jcr:primaryType": "cards:AnswerOption", + "value": "/Vocabularies/Option2" + } + }, + "question_9": { + "jcr:primaryType": "cards:Question", + "text": "Time Question", + "dataType": "time", + "maxAnswers": 1 + } +} \ No newline at end of file diff --git a/modules/data-entry/src/test/resources/SubjectTypes.json b/modules/data-entry/src/test/resources/SubjectTypes.json new file mode 100644 index 0000000000..5620a1c833 --- /dev/null +++ b/modules/data-entry/src/test/resources/SubjectTypes.json @@ -0,0 +1,18 @@ +{ + "jcr:primaryType": "cards:SubjectType", + "label": "Root", + "subjectListLabel" : "Roots", + "cards:defaultOrder": 0, + "Branch": { + "jcr:primaryType": "cards:SubjectType", + "label": "Branch", + "subjectListLabel" : "Branches", + "cards:defaultOrder": 1, + "Leaf": { + "jcr:primaryType": "cards:SubjectType", + "label": "Leaf", + "subjectListLabel" : "Leafs", + "cards:defaultOrder": 2 + } + } +} \ No newline at end of file diff --git a/modules/data-entry/src/test/resources/SubjectTypesPatient.json b/modules/data-entry/src/test/resources/SubjectTypesPatient.json new file mode 100644 index 0000000000..08936f09b3 --- /dev/null +++ b/modules/data-entry/src/test/resources/SubjectTypesPatient.json @@ -0,0 +1,6 @@ +{ + "jcr:primaryType": "cards:SubjectType", + "label": "Patient", + "subjectListLabel" : "Patients", + "cards:defaultOrder": 0 +} \ No newline at end of file diff --git a/modules/data-entry/src/test/resources/TextQuestionnaires.json b/modules/data-entry/src/test/resources/TextQuestionnaires.json new file mode 100644 index 0000000000..6487e4af85 --- /dev/null +++ b/modules/data-entry/src/test/resources/TextQuestionnaires.json @@ -0,0 +1,21 @@ +{ + "jcr:primaryType": "cards:Questionnaire", + "title": "Test Text Questionnaire", + "description": "A test text questionnaire", + "question_1": { + "jcr:primaryType": "cards:Question", + "text": "Text Question", + "dataType": "text", + "maxAnswers": 2 + }, + "question_6": { + "jcr:primaryType": "cards:Question", + "text": "Text1 Question", + "dataType": "text" + }, + "question_10": { + "jcr:primaryType": "cards:Question", + "text": "Text2 Question", + "dataType": "text" + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index f9bd35a3f4..babc851e5d 100644 --- a/pom.xml +++ b/pom.xml @@ -293,6 +293,12 @@ ${oak.version} provided + + org.apache.jackrabbit + oak-jcr + ${oak.version} + provided + org.apache.jackrabbit oak-api @@ -389,7 +395,10 @@ maven-compiler-plugin - -Xlint:all + + -Werror + -Xlint:all + true true true @@ -2044,6 +2053,7 @@ modules + aggregated-cnd aggregated-frontend lfs-resources heracles-resources