diff --git a/src/main/java/org/gridsuite/useradmin/server/RestResponseEntityExceptionHandler.java b/src/main/java/org/gridsuite/useradmin/server/RestResponseEntityExceptionHandler.java index 206e40a..e61240e 100644 --- a/src/main/java/org/gridsuite/useradmin/server/RestResponseEntityExceptionHandler.java +++ b/src/main/java/org/gridsuite/useradmin/server/RestResponseEntityExceptionHandler.java @@ -18,15 +18,14 @@ */ @ControllerAdvice public class RestResponseEntityExceptionHandler { - @ExceptionHandler(value = { UserAdminException.class }) protected ResponseEntity handleException(RuntimeException exception) { - if (exception instanceof UserAdminException) { - UserAdminException userAdminException = (UserAdminException) exception; - if (userAdminException.getType().equals(FORBIDDEN)) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(userAdminException.getType()); - } + if (exception instanceof UserAdminException userAdminException) { + return switch (userAdminException.getType()) { + case FORBIDDEN -> ResponseEntity.status(HttpStatus.FORBIDDEN).body(userAdminException.getType()); + }; + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } diff --git a/src/main/java/org/gridsuite/useradmin/server/UserAdminController.java b/src/main/java/org/gridsuite/useradmin/server/UserAdminController.java index d8a44ee..72c66d6 100644 --- a/src/main/java/org/gridsuite/useradmin/server/UserAdminController.java +++ b/src/main/java/org/gridsuite/useradmin/server/UserAdminController.java @@ -72,4 +72,12 @@ public ResponseEntity userExists(@PathVariable("sub") String sub) { public ResponseEntity> getConnections(@RequestHeader("userId") String userId) { return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(service.getConnections(userId)); } + + @Deprecated(forRemoval = true) + @GetMapping(value = "/connections/deduplicate") + @Operation(summary = "get the connections deduplicated", deprecated = true) + @ApiResponse(responseCode = "200", description = "The connections list") + public ResponseEntity> deduplicateConnections(@RequestHeader("userId") String userId) { + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(service.getDeduplicatedConnections(userId)); + } } diff --git a/src/main/java/org/gridsuite/useradmin/server/UserAdminException.java b/src/main/java/org/gridsuite/useradmin/server/UserAdminException.java index d5ae3f0..158cb9f 100644 --- a/src/main/java/org/gridsuite/useradmin/server/UserAdminException.java +++ b/src/main/java/org/gridsuite/useradmin/server/UserAdminException.java @@ -6,11 +6,16 @@ */ package org.gridsuite.useradmin.server; +import lombok.Data; +import lombok.EqualsAndHashCode; + import java.util.Objects; /** * @author Etienne Homer */ +@EqualsAndHashCode(callSuper = false) +@Data public class UserAdminException extends RuntimeException { public enum Type { FORBIDDEN @@ -22,9 +27,4 @@ public UserAdminException(Type type) { super(Objects.requireNonNull(type.name())); this.type = type; } - - Type getType() { - return type; - } - } diff --git a/src/main/java/org/gridsuite/useradmin/server/repository/AbstractUserEntity.java b/src/main/java/org/gridsuite/useradmin/server/repository/AbstractUserEntity.java new file mode 100644 index 0000000..9f8d312 --- /dev/null +++ b/src/main/java/org/gridsuite/useradmin/server/repository/AbstractUserEntity.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.useradmin.server.repository; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +/** + * @author Etienne Homer + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@MappedSuperclass +abstract class AbstractUserEntity { + public AbstractUserEntity(String sub) { + this(UUID.randomUUID(), sub); + } + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @Column(name = "sub", nullable = false, unique = true) + private String sub; +} diff --git a/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionEntity.java b/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionEntity.java index 89fe2c6..272adfb 100644 --- a/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionEntity.java +++ b/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionEntity.java @@ -6,14 +6,16 @@ */ package org.gridsuite.useradmin.server.repository; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import jakarta.persistence.*; import java.time.LocalDateTime; -import java.util.UUID; /** * @author Etienne Homer @@ -24,25 +26,20 @@ @Setter @Entity @Table(name = "connection", indexes = {@Index(name = "connection_sub_index", columnList = "sub")}) -public class ConnectionEntity { +public class ConnectionEntity extends AbstractUserEntity { + @Column(name = "firstConnectionDate", nullable = false) + private LocalDateTime firstConnectionDate; - @Id - @Column(name = "id") - private UUID id; - - @Column(name = "sub", nullable = false) - private String sub; - - @Column(name = "firstConnexionDate", nullable = false) - private LocalDateTime firstConnexionDate; - - @Column(name = "lastConnexionDate", nullable = false) - private LocalDateTime lastConnexionDate; + @Column(name = "lastConnectionDate", nullable = false) + private LocalDateTime lastConnectionDate; @Column(name = "connectionAccepted", nullable = false) private Boolean connectionAccepted; - public ConnectionEntity(String sub, LocalDateTime firstConnexionDate, LocalDateTime lastConnexionDate, Boolean connectionAccepted) { - this(UUID.randomUUID(), sub, firstConnexionDate, lastConnexionDate, connectionAccepted); + public ConnectionEntity(String sub, LocalDateTime firstConnectionDate, LocalDateTime lastConnectionDate, Boolean connectionAccepted) { + super(sub); + this.firstConnectionDate = firstConnectionDate; + this.lastConnectionDate = lastConnectionDate; + this.connectionAccepted = connectionAccepted; } } diff --git a/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionRepository.java b/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionRepository.java index ec681e1..3621a64 100644 --- a/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionRepository.java +++ b/src/main/java/org/gridsuite/useradmin/server/repository/ConnectionRepository.java @@ -6,10 +6,16 @@ */ package org.gridsuite.useradmin.server.repository; +import jakarta.transaction.Transactional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Repository; -import java.util.List; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Optional; import java.util.UUID; /** @@ -17,5 +23,20 @@ */ @Repository public interface ConnectionRepository extends JpaRepository { - List findBySub(String sub); + @NonNull + Optional findBySub/*IgnoreCase*/(@NonNull String sub); + + @Nullable + ConnectionEntity getBySub(@NonNull String sub); + + @Transactional() + @Modifying + default void recordNewConnection(@NonNull final String sub, final boolean connectionAccepted) { + //To avoid consistency issue we truncate the time to microseconds since postgres and h2 can only store with a precision of microseconds + final LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + this.findBySub/*IgnoreCase*/(sub).ifPresentOrElse( + conn -> this.save(conn.setLastConnectionDate(now).setConnectionAccepted(connectionAccepted)), + () -> this.save(new ConnectionEntity(sub, now, now, connectionAccepted)) + ); + } } diff --git a/src/main/java/org/gridsuite/useradmin/server/repository/UserInfosEntity.java b/src/main/java/org/gridsuite/useradmin/server/repository/UserInfosEntity.java index f14cc29..618a28a 100644 --- a/src/main/java/org/gridsuite/useradmin/server/repository/UserInfosEntity.java +++ b/src/main/java/org/gridsuite/useradmin/server/repository/UserInfosEntity.java @@ -6,33 +6,24 @@ */ package org.gridsuite.useradmin.server.repository; -import lombok.AllArgsConstructor; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.Table; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import jakarta.persistence.*; -import java.util.UUID; - /** * @author Etienne Homer */ @NoArgsConstructor -@AllArgsConstructor +//@AllArgsConstructor @Getter @Setter @Entity @Table(name = "user_infos", indexes = {@Index(name = "user_infos_sub_index", columnList = "sub")}) -public class UserInfosEntity { - +public class UserInfosEntity extends AbstractUserEntity { public UserInfosEntity(String sub) { - this(UUID.randomUUID(), sub); + super(sub); } - - @Id - @Column(name = "id") - private UUID id; - - @Column(name = "sub", nullable = false) - private String sub; } diff --git a/src/main/java/org/gridsuite/useradmin/server/repository/lombok.config b/src/main/java/org/gridsuite/useradmin/server/repository/lombok.config new file mode 100644 index 0000000..8aaaa7d --- /dev/null +++ b/src/main/java/org/gridsuite/useradmin/server/repository/lombok.config @@ -0,0 +1 @@ +lombok.accessors.chain = true diff --git a/src/main/java/org/gridsuite/useradmin/server/service/ConnectionsService.java b/src/main/java/org/gridsuite/useradmin/server/service/ConnectionsService.java index 0b1f5bc..56e05ba 100644 --- a/src/main/java/org/gridsuite/useradmin/server/service/ConnectionsService.java +++ b/src/main/java/org/gridsuite/useradmin/server/service/ConnectionsService.java @@ -11,8 +11,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.Objects; @@ -32,17 +30,10 @@ public ConnectionsService(ConnectionRepository connectionRepository) { @Transactional public void recordConnectionAttempt(String sub, Boolean isAllowed) { - ConnectionEntity connectionEntity = connectionRepository.findBySub(sub).stream().findFirst().orElse(null); - if (connectionEntity == null) { - //To avoid consistency issue we truncate the time to microseconds since postgres and h2 can only store a precision of microseconds - connectionEntity = new ConnectionEntity(sub, LocalDateTime.now().truncatedTo(ChronoUnit.MICROS), LocalDateTime.now().truncatedTo(ChronoUnit.MICROS), isAllowed); - connectionRepository.save(connectionEntity); - } else { - connectionEntity.setLastConnexionDate(LocalDateTime.now().truncatedTo(ChronoUnit.MICROS)); - connectionEntity.setConnectionAccepted(isAllowed); - } + connectionRepository.recordNewConnection(sub, isAllowed); } + @Deprecated(forRemoval = true) @Transactional public List removeDuplicates() { Map> connectionsBySub = connectionRepository.findAll().stream().collect(Collectors.groupingBy(ConnectionEntity::getSub)); @@ -50,11 +41,11 @@ public List removeDuplicates() { connectionsBySub.keySet().forEach(sub -> connectionsBySub.get(sub).stream().skip(1).forEach(connectionEntity -> { ConnectionEntity groupedEntity = connectionsBySub.get(sub).get(0); - if (connectionEntity.getLastConnexionDate().isAfter(groupedEntity.getLastConnexionDate())) { - groupedEntity.setLastConnexionDate(connectionEntity.getLastConnexionDate()); + if (connectionEntity.getLastConnectionDate().isAfter(groupedEntity.getLastConnectionDate())) { + groupedEntity.setLastConnectionDate(connectionEntity.getLastConnectionDate()); } - if (connectionEntity.getFirstConnexionDate().isBefore(groupedEntity.getFirstConnexionDate())) { - groupedEntity.setFirstConnexionDate(connectionEntity.getFirstConnexionDate()); + if (connectionEntity.getFirstConnectionDate().isBefore(groupedEntity.getFirstConnectionDate())) { + groupedEntity.setFirstConnectionDate(connectionEntity.getFirstConnectionDate()); } connectionRepository.delete(connectionEntity); }) diff --git a/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java b/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java index b4960c0..3464afc 100644 --- a/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java +++ b/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java @@ -6,35 +6,34 @@ */ package org.gridsuite.useradmin.server.service; +import lombok.RequiredArgsConstructor; import org.gridsuite.useradmin.server.UserAdminApplicationProps; import org.gridsuite.useradmin.server.UserAdminException; import org.gridsuite.useradmin.server.repository.ConnectionEntity; +import org.gridsuite.useradmin.server.repository.ConnectionRepository; import org.gridsuite.useradmin.server.repository.UserAdminRepository; import org.gridsuite.useradmin.server.repository.UserInfosEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.*; +import java.util.List; +import java.util.UUID; import static org.gridsuite.useradmin.server.UserAdminException.Type.FORBIDDEN; /** * @author Etienne Homer */ +@RequiredArgsConstructor @Service public class UserAdminService { - private UserAdminRepository userAdminRepository; - - private ConnectionsService connectionsService; + private final UserAdminRepository userAdminRepository; + private final ConnectionRepository connectionRepository; + private final ConnectionsService connectionsService; @Autowired private UserAdminApplicationProps applicationProps; - public UserAdminService(UserAdminRepository userAdminRepository, ConnectionsService connectionsService) { - this.userAdminRepository = Objects.requireNonNull(userAdminRepository); - this.connectionsService = Objects.requireNonNull(connectionsService); - } - public List getUsers(String userId) { if (!isAdmin(userId)) { throw new UserAdminException(FORBIDDEN); @@ -43,6 +42,14 @@ public List getUsers(String userId) { } public List getConnections(String userId) { + if (!isAdmin(userId)) { + throw new UserAdminException(FORBIDDEN); + } + return connectionRepository.findAll(); + } + + @Deprecated(forRemoval = true) + public List getDeduplicatedConnections(String userId) { if (!isAdmin(userId)) { throw new UserAdminException(FORBIDDEN); } @@ -65,7 +72,9 @@ public void delete(UUID id, String userId) { } public boolean subExists(String sub) { - Boolean isAllowed = applicationProps.getAdmins().isEmpty() && userAdminRepository.count() == 0 || applicationProps.getAdmins().contains(sub) || !userAdminRepository.findAllBySub(sub).isEmpty(); + Boolean isAllowed = applicationProps.getAdmins().isEmpty() && userAdminRepository.count() == 0 + || applicationProps.getAdmins().contains(sub) + || !userAdminRepository.findAllBySub(sub).isEmpty(); connectionsService.recordConnectionAttempt(sub, isAllowed); return isAllowed.booleanValue(); } diff --git a/src/main/resources/db/changelog/changesets/changelog_20240109T161028Z.xml b/src/main/resources/db/changelog/changesets/changelog_20240109T161028Z.xml new file mode 100644 index 0000000..ede53a5 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20240109T161028Z.xml @@ -0,0 +1,40 @@ + + + + Deduplicate rows before adding unique constraints + + id in (select id from (select row_number() over (partition by sub) as rnb, * from user_infos) subquery where rnb > 1) + + + + update connection c1 set + first_connexion_date = (select min(c2.first_connexion_date) from connection c2 where c2.sub=c1.sub group by c2.sub), + last_connexion_date = (select max(c2.last_connexion_date) from connection c2 where c2.sub=c1.sub group by c2.sub), + connection_accepted = (select connection_accepted from connection c2 where (c2.sub, c2.last_connexion_date) in ( + select c3.sub, max(c3.last_connexion_date) from connection c3 where c3.sub=c1.sub group by c3.sub) limit 1); + + + --oracle use fetch instead of limit + update connection c1 set + first_connexion_date = (select min(c2.first_connexion_date) from connection c2 where c2.sub=c1.sub group by c2.sub), + last_connexion_date = (select max(c2.last_connexion_date) from connection c2 where c2.sub=c1.sub group by c2.sub), + connection_accepted = (select connection_accepted from connection c2 where (c2.sub, c2.last_connexion_date) in ( + select c3.sub, max(c3.last_connexion_date) from connection c3 where c3.sub=c1.sub group by c3.sub) FETCH FIRST 1 ROW ONLY); + + + id in (select id from (select row_number() over (partition by sub) as rnb, * from connection) subquery where rnb > 1) + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index d4cc4f1..0f75814 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -1,9 +1,10 @@ databaseChangeLog: - - include: file: changesets/changelog_20220921T091049Z.xml relativeToChangelogFile: true - - include: file: changesets/changelog_20220930T080149Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20240109T161028Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java b/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java index 0074c12..7bcfaff 100644 --- a/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.assertj.core.api.WithAssertions; import org.gridsuite.useradmin.server.repository.ConnectionEntity; import org.gridsuite.useradmin.server.repository.ConnectionRepository; import org.gridsuite.useradmin.server.repository.UserAdminRepository; @@ -18,16 +19,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.List; import java.util.UUID; -import static org.junit.Assert.*; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -39,7 +39,7 @@ @AutoConfigureMockMvc @SpringBootTest @ContextConfiguration(classes = {UserAdminApplication.class}) -public class UserAdminTest { +public class UserAdminTest implements WithAssertions { @Autowired private MockMvc mockMvc; @@ -78,10 +78,9 @@ public void testUserAdmin() throws Exception { .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), - new TypeReference<>() { - }); + new TypeReference<>() { }); - assertEquals(0, userEntities.size()); + assertThat(userEntities).isEmpty(); mockMvc.perform(head("/" + UserAdminApi.API_VERSION + "/users/{sub}", ADMIN_USER)) .andExpect(status().isOk()) @@ -99,10 +98,9 @@ public void testUserAdmin() throws Exception { .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), - new TypeReference<>() { - }); + new TypeReference<>() { }); - assertEquals(1, userEntities.size()); + assertThat(userEntities).hasSize(1); UUID userId = userEntities.get(0).getId(); @@ -113,17 +111,20 @@ public void testUserAdmin() throws Exception { mockMvc.perform(head("/" + UserAdminApi.API_VERSION + "/users/{sub}", "UNKNOWN")) .andExpect(status().isNoContent()) .andReturn(); - assertEquals(3, connectionRepository.findAll().size()); - assertTrue(connectionRepository.findBySub(USER_SUB).get(0).getConnectionAccepted()); - assertFalse(connectionRepository.findBySub("UNKNOWN").get(0).getConnectionAccepted()); - LocalDateTime firstConnectionDate = connectionRepository.findBySub(USER_SUB).get(0).getFirstConnexionDate(); + assertThat(connectionRepository.findAll()).hasSize(3); + assertThat(connectionRepository.findBySub(USER_SUB)).get() + .extracting(ConnectionEntity::getConnectionAccepted, BOOLEAN).isTrue(); + assertThat(connectionRepository.findBySub("UNKNOWN")).get() + .extracting(ConnectionEntity::getConnectionAccepted, BOOLEAN).isFalse(); + LocalDateTime firstConnectionDate = connectionRepository.findBySub(USER_SUB).get().getFirstConnectionDate(); //firstConnectionDate and lastConnectionDate are equals cause this is the first connection for this user - assertTrue(firstConnectionDate.toEpochSecond(ZoneOffset.UTC) < connectionRepository.findBySub(USER_SUB).get(0).getLastConnexionDate().toEpochSecond(ZoneOffset.UTC) + 2); + assertThat(connectionRepository.findBySub(USER_SUB)).get() + .extracting(ConnectionEntity::getLastConnectionDate, LOCAL_DATE_TIME).isAfterOrEqualTo(firstConnectionDate); mockMvc.perform(head("/" + UserAdminApi.API_VERSION + "/users/{sub}", USER_SUB)) .andExpect(status().isOk()) .andReturn(); - assertEquals(firstConnectionDate, connectionRepository.findBySub(USER_SUB).get(0).getFirstConnexionDate()); + assertThat(connectionRepository.findBySub(USER_SUB)).get().extracting(ConnectionEntity::getFirstConnectionDate, LOCAL_DATE_TIME).isEqualTo(firstConnectionDate); mockMvc.perform(delete("/" + UserAdminApi.API_VERSION + "/users/{id}", userId) .header("userId", ADMIN_USER) @@ -137,9 +138,8 @@ public void testUserAdmin() throws Exception { .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), - new TypeReference<>() { - }); - assertEquals(0, userEntities.size()); + new TypeReference<>() { }); + assertThat(userEntities).isEmpty(); mockMvc.perform(delete("/" + UserAdminApi.API_VERSION + "/users/{id}", userId) .header("userId", NOT_ADMIN) @@ -180,10 +180,9 @@ public void testGetConnections() throws Exception { .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), - new TypeReference<>() { - }); + new TypeReference<>() { }); - assertEquals(2, userEntities.size()); + assertThat(userEntities).hasSize(2); mockMvc.perform(head("/" + UserAdminApi.API_VERSION + "/users/{sub}", USER_SUB)) .andExpect(status().isOk()) @@ -199,14 +198,21 @@ public void testGetConnections() throws Exception { .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), - new TypeReference<>() { - }); - - assertEquals(2, connectionEntities.size()); - - connectionRepository.save(new ConnectionEntity(USER_SUB, LocalDateTime.now(), LocalDateTime.now(), true)); - connectionRepository.save(new ConnectionEntity(USER_SUB, LocalDateTime.now().minusSeconds(5), LocalDateTime.now(), true)); - connectionRepository.save(new ConnectionEntity(USER_SUB2, LocalDateTime.now(), LocalDateTime.now(), false)); + new TypeReference<>() { }); + + assertThat(connectionEntities).hasSize(2); + + assertThatThrownBy(() -> connectionRepository.save(new ConnectionEntity(USER_SUB, LocalDateTime.now(), LocalDateTime.now(), true))) + .isInstanceOf(DataIntegrityViolationException.class) + .hasMessageContaining("Unique index or primary key violation"); + connectionRepository.save(connectionRepository.getBySub(USER_SUB) + .setFirstConnectionDate(LocalDateTime.now().minusSeconds(5)) + .setLastConnectionDate(LocalDateTime.now()) + .setConnectionAccepted(true)); + connectionRepository.save(connectionRepository.getBySub(USER_SUB2) + .setFirstConnectionDate(LocalDateTime.now()) + .setLastConnectionDate(LocalDateTime.now()) + .setConnectionAccepted(false)); connectionEntities = objectMapper.readValue( mockMvc.perform(get("/" + UserAdminApi.API_VERSION + "/connections") @@ -214,9 +220,8 @@ public void testGetConnections() throws Exception { .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), - new TypeReference<>() { - }); - assertEquals(2, connectionEntities.size()); + new TypeReference<>() { }); + assertThat(connectionEntities).hasSize(2); mockMvc.perform(get("/" + UserAdminApi.API_VERSION + "/connections") .header("userId", NOT_ADMIN)