diff --git a/src/main/java/cz/cvut/kbss/study/model/Institution.java b/src/main/java/cz/cvut/kbss/study/model/Institution.java
index 8a1ada98..580a46bd 100644
--- a/src/main/java/cz/cvut/kbss/study/model/Institution.java
+++ b/src/main/java/cz/cvut/kbss/study/model/Institution.java
@@ -88,8 +88,8 @@ public void setReferenceId(Integer referenceId) {
@Override
public String toString() {
- return "Institution{" +
- "name='" + name + '\'' +
- "} " + super.toString();
+ return "Institution{<" + uri +
+ ">, name='" + name + '\'' +
+ "}";
}
}
diff --git a/src/main/java/cz/cvut/kbss/study/model/PatientRecord.java b/src/main/java/cz/cvut/kbss/study/model/PatientRecord.java
index f64db877..2adf4ec6 100644
--- a/src/main/java/cz/cvut/kbss/study/model/PatientRecord.java
+++ b/src/main/java/cz/cvut/kbss/study/model/PatientRecord.java
@@ -10,13 +10,14 @@
import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraints;
import cz.cvut.kbss.study.model.qam.Question;
import cz.cvut.kbss.study.model.util.HasOwlKey;
+import cz.cvut.kbss.study.model.util.HasUri;
import java.io.Serializable;
import java.net.URI;
import java.util.Date;
@OWLClass(iri = Vocabulary.s_c_patient_record)
-public class PatientRecord implements Serializable, HasOwlKey {
+public class PatientRecord implements Serializable, HasOwlKey, HasUri {
@Id
private URI uri;
@@ -49,9 +50,10 @@ public class PatientRecord implements Serializable, HasOwlKey {
private String formTemplate;
@OWLObjectProperty(iri = Vocabulary.s_p_has_question, cascade = {CascadeType.MERGE,
- CascadeType.REMOVE}, fetch = FetchType.EAGER)
+ CascadeType.REMOVE}, fetch = FetchType.EAGER)
private Question question;
+ @Override
public URI getUri() {
return uri;
}
@@ -136,10 +138,10 @@ public void setFormTemplate(String formTemplate) {
@Override
public String toString() {
- return "PatientRecord{" +
- "localName=" + localName +
+ return "PatientRecord{<" + uri +
+ ">, localName=" + localName +
", dateCreated=" + dateCreated +
", institution=" + institution +
- "} " + super.toString();
+ "}";
}
}
diff --git a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java
index 186b39cf..27e925e0 100644
--- a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java
+++ b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java
@@ -19,6 +19,9 @@
import java.math.BigInteger;
import java.net.URI;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
import java.util.List;
import java.util.Objects;
@@ -164,4 +167,71 @@ public void requireUniqueNonEmptyLocalName(PatientRecord entity) {
}
em.clear();
}
+
+ /**
+ * Retrieves records modified (created or modified) in the specified time interval.
+ *
+ * Since the record modification is tracked by a timestamp and the arguments here are dates, this method uses
+ * beginning of the min date and end of the max date.
+ *
+ * @param minDate Minimum date of modification of matching records, inclusive
+ * @param maxDate Maximum date of modification of matching records, inclusive
+ * @return List of matching records
+ */
+ public List findAllFull(LocalDate minDate, LocalDate maxDate) {
+ Objects.requireNonNull(minDate);
+ Objects.requireNonNull(maxDate);
+
+ final Instant min = minDate.atStartOfDay(ZoneOffset.UTC).toInstant();
+ final Instant max = maxDate.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant();
+
+ return em.createNativeQuery("SELECT ?r WHERE {" +
+ "?r a ?type ;" +
+ "?hasCreatedDate ?created ." +
+ "OPTIONAL { ?r ?hasLastModified ?lastModified . }" +
+ "BIND (IF (BOUND(?lastModified), ?lastModified, ?created) AS ?edited)" +
+ "FILTER (?edited >= ?minDate && ?edited < ?maxDate)" +
+ "} ORDER BY DESC(?edited)", PatientRecord.class)
+ .setParameter("type", typeUri)
+ .setParameter("hasCreatedDate", URI.create(Vocabulary.s_p_created))
+ .setParameter("hasLastModified", URI.create(Vocabulary.s_p_modified))
+ .setParameter("minDate", min)
+ .setParameter("maxDate", max).getResultList();
+ }
+
+ /**
+ * Retrieves records modified (created or modified) in the specified time interval.
+ *
+ * Since the record modification is tracked by a timestamp and the arguments here are dates, this method uses
+ * beginning of the min date and end of the max date.
+ *
+ * @param institution Institution with which matching records have to be associated
+ * @param minDate Minimum date of modification of matching records, inclusive
+ * @param maxDate Maximum date of modification of matching records, inclusive
+ * @return List of matching records
+ */
+ public List findAllFull(Institution institution, LocalDate minDate, LocalDate maxDate) {
+ Objects.requireNonNull(institution);
+ Objects.requireNonNull(minDate);
+ Objects.requireNonNull(maxDate);
+
+ final Instant min = minDate.atStartOfDay(ZoneOffset.UTC).toInstant();
+ final Instant max = maxDate.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant();
+
+ return em.createNativeQuery("SELECT ?r WHERE {" +
+ "?r a ?type ; " +
+ "?hasCreatedDate ?created ; " +
+ "?hasInstitution ?institution . " +
+ "OPTIONAL { ?r ?hasLastModified ?lastModified . } " +
+ "BIND (IF (BOUND(?lastModified), ?lastModified, ?created) AS ?edited) " +
+ "FILTER (?edited >= ?minDate && ?edited < ?maxDate)" +
+ "} ORDER BY DESC(?edited)", PatientRecord.class)
+ .setParameter("type", typeUri)
+ .setParameter("hasInstitution", URI.create(Vocabulary.s_p_was_treated_at))
+ .setParameter("institution", institution)
+ .setParameter("hasCreatedDate", URI.create(Vocabulary.s_p_created))
+ .setParameter("hasLastModified", URI.create(Vocabulary.s_p_modified))
+ .setParameter("minDate", min)
+ .setParameter("maxDate", max).getResultList();
+ }
}
diff --git a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java
index 77fd3646..1dc87cfe 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java
@@ -9,15 +9,25 @@
import cz.cvut.kbss.study.security.SecurityConstants;
import cz.cvut.kbss.study.service.InstitutionService;
import cz.cvut.kbss.study.service.PatientRecordService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import java.time.LocalDate;
import java.util.List;
+import java.util.Optional;
@RestController
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_USER + "')")
@@ -35,16 +45,33 @@ public PatientRecordController(PatientRecordService recordService, InstitutionSe
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)")
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
- public List getRecords(@RequestParam(value = "institution", required = false) String institutionKey) {
- return institutionKey != null ? findByInstitution(institutionKey) : recordService.findAllRecords();
+ public List getRecords(
+ @RequestParam(value = "institution", required = false) String institutionKey) {
+ return institutionKey != null ? recordService.findByInstitution(getInstitution(institutionKey)) :
+ recordService.findAllRecords();
}
- private List findByInstitution(String institutionKey) {
+ private Institution getInstitution(String institutionKey) {
final Institution institution = institutionService.findByKey(institutionKey);
if (institution == null) {
throw NotFoundException.create("Institution", institutionKey);
}
- return recordService.findByInstitution(institution);
+ return institution;
+ }
+
+ @PreAuthorize(
+ "hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)")
+ @GetMapping(value = "/export", produces = MediaType.APPLICATION_JSON_VALUE)
+ public List exportRecords(
+ @RequestParam(value = "institution", required = false) String institutionKey,
+ @RequestParam(name = "minDate", required = false) Optional minDateParam,
+ @RequestParam(name = "maxDate", required = false) Optional maxDateParam) {
+ final LocalDate minDate = minDateParam.orElse(LocalDate.EPOCH);
+ final LocalDate maxDate = maxDateParam.orElse(LocalDate.now());
+ if (institutionKey != null) {
+ return recordService.findAllFull(getInstitution(institutionKey), minDate, maxDate);
+ }
+ return recordService.findAllFull(minDate, maxDate);
}
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isRecordInUsersInstitution(#key)")
diff --git a/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java b/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java
index 078021ea..0ebc2892 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java
@@ -1,16 +1,17 @@
package cz.cvut.kbss.study.rest.util;
-import cz.cvut.kbss.study.exception.WebServiceIntegrationException;
-import cz.cvut.kbss.study.util.Constants;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
-import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
public class RestUtils {
@@ -79,4 +80,22 @@ public static String getCookie(HttpServletRequest request, String cookieName) {
}
return null;
}
+
+ /**
+ * Parses the specified date string.
+ *
+ * The parameter is expected to be in the ISO format.
+ *
+ * @param dateStr Date string
+ * @return {@code LocalDate} object corresponding to the specified date string
+ * @throws ResponseStatusException Bad request is thrown if the date string is not parseable
+ */
+ public static LocalDate parseDate(String dateStr) {
+ try {
+ return LocalDate.parse(dateStr);
+ } catch (DateTimeParseException | NullPointerException e) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Value '" + dateStr + "' is not a valid date in ISO format.");
+ }
+ }
+
}
diff --git a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java
index 5f6920ca..5bf775a5 100644
--- a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java
+++ b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java
@@ -5,6 +5,7 @@
import cz.cvut.kbss.study.model.PatientRecord;
import cz.cvut.kbss.study.model.User;
+import java.time.LocalDate;
import java.util.List;
public interface PatientRecordService extends BaseService {
@@ -39,4 +40,31 @@ public interface PatientRecordService extends BaseService {
* @return Records of matching patients
*/
List findAllRecords();
+
+ /**
+ * Finds all records that were created or modified in the specified date interval.
+ *
+ * In contrast to {@link #findAll()}, this method returns full records, not DTOs.
+ *
+ * @param minDate Minimum date of modification of returned records, inclusive
+ * @param maxDate Maximum date of modification of returned records, inclusive
+ * @return List of matching records
+ * @see #findAllFull(Institution, LocalDate, LocalDate)
+ * @see #findAllRecords()
+ */
+ List findAllFull(LocalDate minDate, LocalDate maxDate);
+
+ /**
+ * Finds all records that were created or modified at the specified institution in the specified date interval.
+ *
+ * In contrast to {@link #findByInstitution(Institution)}, this method returns full records, not DTOs.
+ *
+ * @param institution Institution with which the records are associated
+ * @param minDate Minimum date of modification of returned records, inclusive
+ * @param maxDate Maximum date of modification of returned records, inclusive
+ * @return List of matching records
+ * @see #findAllFull(LocalDate, LocalDate)
+ * @see #findByInstitution(Institution)
+ */
+ List findAllFull(Institution institution, LocalDate minDate, LocalDate maxDate);
}
diff --git a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java
index dd45e36c..87e1c2a7 100644
--- a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java
+++ b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java
@@ -12,6 +12,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.time.LocalDate;
import java.util.Date;
import java.util.List;
@@ -52,6 +53,18 @@ public List findAllRecords() {
return recordDao.findAllRecords();
}
+ @Transactional(readOnly = true)
+ @Override
+ public List findAllFull(LocalDate minDate, LocalDate maxDate) {
+ return recordDao.findAllFull(minDate, maxDate);
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List findAllFull(Institution institution, LocalDate minDate, LocalDate maxDate) {
+ return recordDao.findAllFull(institution, minDate, maxDate);
+ }
+
@Override
protected void prePersist(PatientRecord instance) {
final User author = securityUtils.getCurrentUser();
diff --git a/src/test/java/cz/cvut/kbss/study/environment/util/ContainsSameEntities.java b/src/test/java/cz/cvut/kbss/study/environment/util/ContainsSameEntities.java
new file mode 100644
index 00000000..1b25d4a1
--- /dev/null
+++ b/src/test/java/cz/cvut/kbss/study/environment/util/ContainsSameEntities.java
@@ -0,0 +1,61 @@
+/*
+ * TermIt
+ * Copyright (C) 2023 Czech Technical University in Prague
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package cz.cvut.kbss.study.environment.util;
+
+import cz.cvut.kbss.study.model.util.HasUri;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Checks whether the provided collection contains the same entities as the expected one.
+ *
+ * The membership check is done based on entity URIs.
+ */
+public class ContainsSameEntities extends TypeSafeMatcher> {
+
+ private final Collection extends HasUri> expected;
+
+ public ContainsSameEntities(Collection extends HasUri> expected) {
+ this.expected = Objects.requireNonNull(expected);
+ }
+
+ @Override
+ protected boolean matchesSafely(Collection extends HasUri> actual) {
+ if (actual == null || actual.size() != expected.size()) {
+ return false;
+ }
+ for (HasUri e : expected) {
+ if (actual.stream().noneMatch(ee -> Objects.equals(e.getUri(), ee.getUri()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValueList("[", ", ", "]", expected);
+ }
+
+ public static ContainsSameEntities containsSameEntities(Collection extends HasUri> expected) {
+ return new ContainsSameEntities(expected);
+ }
+}
diff --git a/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java b/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java
index e4b16fc3..52f7d604 100644
--- a/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java
+++ b/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java
@@ -21,6 +21,8 @@ public class Environment {
private static ObjectMapper objectMapper;
+ public static long MILLIS_PER_DAY = 24 * 3600 * 1000L;
+
private Environment() {
throw new AssertionError();
}
diff --git a/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java b/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java
index 9713bc62..817a3070 100644
--- a/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java
+++ b/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java
@@ -6,6 +6,7 @@
import cz.cvut.kbss.jopa.model.metamodel.EntityType;
import cz.cvut.kbss.study.dto.PatientRecordDto;
import cz.cvut.kbss.study.environment.generator.Generator;
+import cz.cvut.kbss.study.environment.util.Environment;
import cz.cvut.kbss.study.model.Institution;
import cz.cvut.kbss.study.model.PatientRecord;
import cz.cvut.kbss.study.model.User;
@@ -16,9 +17,17 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
+import java.util.stream.IntStream;
+import static cz.cvut.kbss.study.environment.util.ContainsSameEntities.containsSameEntities;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class PatientRecordDaoTest extends BaseDaoTestRunner {
@@ -231,4 +240,63 @@ void findByKeyLoadsRecordByKey() {
assertEquals(record.getUri(), result.getUri());
assertNotNull(result.getQuestion());
}
+
+ private void persistRecordWithIdentification(PatientRecord record) {
+ record.setKey(IdentificationUtils.generateKey());
+ record.setUri(PatientRecordDao.generateRecordUriFromKey(record.getKey()));
+ em.persist(record, getDescriptor(record));
+ }
+
+ @Test
+ void findAllFullReturnsRecordsMatchingSpecifiedDatePeriod() {
+ final User author = generateAuthorWithInstitution();
+ final List allRecords = generateRecordsForAuthor(author);
+ transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
+ final LocalDate minDate = LocalDate.now().minusDays(3);
+ final LocalDate maxDate = LocalDate.now().minusDays(1);
+ final List expected = allRecords.stream().filter(r -> {
+ final Date modified = r.getLastModified() != null ? r.getLastModified() : r.getDateCreated();
+ final LocalDate modifiedDate = modified.toInstant().atZone(ZoneOffset.UTC).toLocalDate();
+ return !modifiedDate.isBefore(minDate) && !modifiedDate.isAfter(maxDate);
+ }).toList();
+
+ final List result = sut.findAllFull(minDate, maxDate);
+ assertFalse(result.isEmpty());
+ assertThat(result, containsSameEntities(expected));
+ }
+
+ private List generateRecordsForAuthor(User author) {
+ return IntStream.range(0, 5).mapToObj(i -> {
+ final PatientRecord r = Generator.generatePatientRecord(author);
+ if (Generator.randomBoolean()) {
+ r.setDateCreated(new Date(System.currentTimeMillis() - i * Environment.MILLIS_PER_DAY));
+ } else {
+ r.setDateCreated(new Date(System.currentTimeMillis() - 365 * Environment.MILLIS_PER_DAY));
+ r.setLastModified(new Date(System.currentTimeMillis() - i * Environment.MILLIS_PER_DAY));
+ }
+ return r;
+ }).toList();
+ }
+
+ @Test
+ void findAllFullReturnsRecordsMatchingSpecifiedDatePeriodAndInstitution() {
+ final User authorOne = generateAuthorWithInstitution();
+ final Institution institution = authorOne.getInstitution();
+ final User authorTwo = generateAuthorWithInstitution();
+ final List allRecords = new ArrayList<>(generateRecordsForAuthor(authorOne));
+ allRecords.addAll(generateRecordsForAuthor(authorTwo));
+ transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
+ final LocalDate minDate = LocalDate.now().minusDays(3);
+ final LocalDate maxDate = LocalDate.now().minusDays(1);
+ final List expected = allRecords.stream().filter(r -> {
+ final Date modified = r.getLastModified() != null ? r.getLastModified() : r.getDateCreated();
+ final LocalDate modifiedDate = modified.toInstant().atZone(ZoneOffset.UTC).toLocalDate();
+ return !modifiedDate.isBefore(minDate) && !modifiedDate.isAfter(maxDate) && r.getInstitution().getUri()
+ .equals(institution.getUri());
+ }).toList();
+
+ final List result = sut.findAllFull(institution, minDate, maxDate);
+ assertFalse(result.isEmpty());
+ assertThat(result, containsSameEntities(expected));
+ }
}
diff --git a/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java b/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java
index 88806edc..9c2cffee 100644
--- a/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java
+++ b/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java
@@ -9,27 +9,33 @@
import cz.cvut.kbss.study.model.User;
import cz.cvut.kbss.study.service.InstitutionService;
import cz.cvut.kbss.study.service.PatientRecordService;
+import cz.cvut.kbss.study.util.IdentificationUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
+import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static cz.cvut.kbss.study.environment.util.ContainsSameEntities.containsSameEntities;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
@ExtendWith(MockitoExtension.class)
public class PatientRecordControllerTest extends BaseControllerTestRunner {
@@ -43,11 +49,14 @@ public class PatientRecordControllerTest extends BaseControllerTestRunner {
@InjectMocks
private PatientRecordController controller;
+ private User user;
+
@BeforeEach
public void setUp() {
super.setUp(controller);
Institution institution = Generator.generateInstitution();
- User user = Generator.generateUser(institution);
+ institution.setKey(IdentificationUtils.generateKey());
+ this.user = Generator.generateUser(institution);
Environment.setCurrentUser(user);
}
@@ -64,13 +73,14 @@ public void getRecordThrowsNotFoundWhenReportIsNotFound() throws Exception {
@Test
public void getRecordReturnsFoundRecord() throws Exception {
final String key = "12345";
- PatientRecord patientRecord = Generator.generatePatientRecord(Environment.getCurrentUser());
+ PatientRecord patientRecord = Generator.generatePatientRecord(user);
when(patientRecordServiceMock.findByKey(key)).thenReturn(patientRecord);
final MvcResult result = mockMvc.perform(get("/records/" + key)).andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
- final PatientRecord res = objectMapper.readValue(result.getResponse().getContentAsString(), PatientRecord.class);
- assertEquals(res.getUri(),patientRecord.getUri());
+ final PatientRecord res =
+ objectMapper.readValue(result.getResponse().getContentAsString(), PatientRecord.class);
+ assertEquals(res.getUri(), patientRecord.getUri());
verify(patientRecordServiceMock).findByKey(key);
}
@@ -82,8 +92,8 @@ public void getRecordsReturnsEmptyListWhenNoReportsAreFound() throws Exception {
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertTrue(body.isEmpty());
}
@@ -108,8 +118,8 @@ public void getRecordsReturnsAllRecords() throws Exception {
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals(3, body.size());
verify(patientRecordServiceMock).findAllRecords();
}
@@ -133,12 +143,13 @@ public void finByInstitutionReturnsRecords() throws Exception {
when(institutionServiceMock.findByKey(institution.getKey())).thenReturn(institution);
when(patientRecordServiceMock.findByInstitution(institution)).thenReturn(records);
System.out.println(institution.getKey());
- final MvcResult result = mockMvc.perform(get("/records").param("institution", institution.getKey())).andReturn();
+ final MvcResult result =
+ mockMvc.perform(get("/records").param("institution", institution.getKey())).andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals(2, body.size());
verify(institutionServiceMock).findByKey(institution.getKey());
}
@@ -155,12 +166,11 @@ public void findByInstitutionReturnsNotFound() throws Exception {
@Test
public void createRecordReturnsResponseStatusCreated() throws Exception {
- Institution institution = Generator.generateInstitution();
- User user = Generator.generateUser(institution);
PatientRecord record = Generator.generatePatientRecord(user);
final MvcResult result = mockMvc.perform(post("/records").content(toJson(record))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ .contentType(MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.CREATED, HttpStatus.valueOf(result.getResponse().getStatus()));
}
@@ -169,15 +179,14 @@ public void createRecordReturnsResponseStatusCreated() throws Exception {
public void updateRecordReturnsResponseStatusNoContent() throws Exception {
final String key = "12345";
- Institution institution = Generator.generateInstitution();
- User user = Generator.generateUser(institution);
PatientRecord record = Generator.generatePatientRecord(user);
record.setKey(key);
when(patientRecordServiceMock.findByKey(key)).thenReturn(record);
final MvcResult result = mockMvc.perform(put("/records/" + key).content(toJson(record))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ .contentType(MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.NO_CONTENT, HttpStatus.valueOf(result.getResponse().getStatus()));
verify(patientRecordServiceMock).findByKey(key);
@@ -187,13 +196,12 @@ public void updateRecordReturnsResponseStatusNoContent() throws Exception {
public void updateRecordWithNonMatchingKeyReturnsResponseStatusBadRequest() throws Exception {
final String key = "12345";
- Institution institution = Generator.generateInstitution();
- User user = Generator.generateUser(institution);
PatientRecord record = Generator.generatePatientRecord(user);
record.setKey(key);
- final MvcResult result = mockMvc.perform(put("/records/123456" ).content(toJson(record))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ final MvcResult result = mockMvc.perform(put("/records/123456").content(toJson(record))
+ .contentType(MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.BAD_REQUEST, HttpStatus.valueOf(result.getResponse().getStatus()));
}
@@ -202,15 +210,14 @@ public void updateRecordWithNonMatchingKeyReturnsResponseStatusBadRequest() thro
public void updateRecordReturnsResponseStatusNotFound() throws Exception {
final String key = "12345";
- Institution institution = Generator.generateInstitution();
- User user = Generator.generateUser(institution);
PatientRecord record = Generator.generatePatientRecord(user);
record.setKey(key);
when(patientRecordServiceMock.findByKey(key)).thenReturn(null);
final MvcResult result = mockMvc.perform(put("/records/" + key).content(toJson(record))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ .contentType(MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.NOT_FOUND, HttpStatus.valueOf(result.getResponse().getStatus()));
verify(patientRecordServiceMock).findByKey(key);
@@ -220,17 +227,81 @@ public void updateRecordReturnsResponseStatusNotFound() throws Exception {
public void deleteRecordReturnsResponseStatusNoContent() throws Exception {
final String key = "12345";
- Institution institution = Generator.generateInstitution();
- User user = Generator.generateUser(institution);
PatientRecord record = Generator.generatePatientRecord(user);
record.setKey(key);
when(patientRecordServiceMock.findByKey(key)).thenReturn(record);
- final MvcResult result = mockMvc.perform(delete("/records/12345" )).andReturn();
+ final MvcResult result = mockMvc.perform(delete("/records/12345")).andReturn();
assertEquals(HttpStatus.NO_CONTENT, HttpStatus.valueOf(result.getResponse().getStatus()));
verify(patientRecordServiceMock).findByKey(key);
}
+ @Test
+ void exportRecordsParsesProvidedDateBoundsAndPassesThemToService() throws Exception {
+ final LocalDate minDate = LocalDate.now().minusDays(35);
+ final LocalDate maxDate = LocalDate.now().minusDays(5);
+ final List records =
+ List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
+ when(patientRecordServiceMock.findAllFull(any(), any())).thenReturn(records);
+
+ final MvcResult mvcResult = mockMvc.perform(get("/records/export")
+ .param("minDate", minDate.toString())
+ .param("maxDate", maxDate.toString()))
+ .andReturn();
+ final List result = readValue(mvcResult, new TypeReference<>() {
+ });
+ assertThat(result, containsSameEntities(records));
+ verify(patientRecordServiceMock).findAllFull(minDate, maxDate);
+ }
+
+ @Test
+ void exportRecordsUsesDefaultValuesForMinAndMaxDateWhenTheyAreNotProvidedByRequest() throws Exception {
+ final List records =
+ List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
+ when(patientRecordServiceMock.findAllFull(any(), any())).thenReturn(records);
+
+ final MvcResult mvcResult = mockMvc.perform(get("/records/export")).andReturn();
+ final List result = readValue(mvcResult, new TypeReference<>() {
+ });
+ assertThat(result, containsSameEntities(records));
+ verify(patientRecordServiceMock).findAllFull(LocalDate.EPOCH, LocalDate.now());
+ }
+
+ @Test
+ void exportRecordsExportsRecordsForProvidedInstitutionForSpecifiedPeriod() throws Exception {
+ final LocalDate minDate = LocalDate.now().minusDays(35);
+ final LocalDate maxDate = LocalDate.now().minusDays(5);
+ final List records =
+ List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
+ when(institutionServiceMock.findByKey(user.getInstitution().getKey())).thenReturn(user.getInstitution());
+ when(patientRecordServiceMock.findAllFull(any(), any(), any())).thenReturn(records);
+
+ final MvcResult mvcResult = mockMvc.perform(get("/records/export")
+ .param("minDate", minDate.toString())
+ .param("maxDate", maxDate.toString())
+ .param("institution", user.getInstitution().getKey()))
+ .andReturn();
+ final List result = readValue(mvcResult, new TypeReference<>() {
+ });
+ assertThat(result, containsSameEntities(records));
+ verify(patientRecordServiceMock).findAllFull(user.getInstitution(), minDate, maxDate);
+ }
+
+ @Test
+ void exportRecordsExportsRecordsForProvidedInstitutionWithDefaultDatesWhenNoneAreProvided() throws Exception {
+ final List records =
+ List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
+ when(institutionServiceMock.findByKey(user.getInstitution().getKey())).thenReturn(user.getInstitution());
+ when(patientRecordServiceMock.findAllFull(any(), any(), any())).thenReturn(records);
+
+ final MvcResult mvcResult = mockMvc.perform(get("/records/export").
+ param("institution", user.getInstitution().getKey()))
+ .andReturn();
+ final List result = readValue(mvcResult, new TypeReference<>() {
+ });
+ assertThat(result, containsSameEntities(records));
+ verify(patientRecordServiceMock).findAllFull(user.getInstitution(), LocalDate.EPOCH, LocalDate.now());
+ }
}
diff --git a/src/test/java/cz/cvut/kbss/study/rest/util/RestUtilsTest.java b/src/test/java/cz/cvut/kbss/study/rest/util/RestUtilsTest.java
new file mode 100644
index 00000000..a2045f83
--- /dev/null
+++ b/src/test/java/cz/cvut/kbss/study/rest/util/RestUtilsTest.java
@@ -0,0 +1,35 @@
+package cz.cvut.kbss.study.rest.util;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.time.LocalDate;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class RestUtilsTest {
+
+ @Test
+ void parseTimestampReturnsLocalDateParsedFromSpecifiedString() {
+ final LocalDate value = LocalDate.now();
+ assertEquals(value, RestUtils.parseDate(value.toString()));
+ }
+
+ @Test
+ void parseTimestampThrowsResponseStatusExceptionWithStatus400ForUnparseableString() {
+ final Date date = new Date();
+ final ResponseStatusException ex = assertThrows(ResponseStatusException.class,
+ () -> RestUtils.parseDate(date.toString()));
+ assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode());
+ }
+
+ @Test
+ void parseTimestampThrowsResponseStatusExceptionWithStatus400ForNullArgument() {
+ final ResponseStatusException ex = assertThrows(ResponseStatusException.class,
+ () -> RestUtils.parseDate(null));
+ assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode());
+ }
+}
\ No newline at end of file