implement service.upsert
This commit is contained in:
@@ -1,4 +1,50 @@
|
|||||||
package com.spijkerman.ivo.threekidfamily.domain.person;
|
package com.spijkerman.ivo.threekidfamily.domain.person;
|
||||||
|
|
||||||
public record Person() {
|
import jakarta.annotation.Nullable;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Accessors(fluent = true)
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Person {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@NonNull
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private LocalDate birthDate;
|
||||||
|
|
||||||
|
// Store only relation IDs instead of JPA mappings to reduce JPA/Hibernate tomfoolery
|
||||||
|
@Nullable
|
||||||
|
private Integer partnerId;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Singular
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<Integer> childIds = new HashSet<>();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Singular
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
private Set<Integer> parentIds = new HashSet<>();
|
||||||
|
|
||||||
|
public Person(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package com.spijkerman.ivo.threekidfamily.domain.person;
|
|||||||
import com.spijkerman.ivo.threekidfamily.domain.person.dto.PersonUpsertRequest;
|
import com.spijkerman.ivo.threekidfamily.domain.person.dto.PersonUpsertRequest;
|
||||||
import com.spijkerman.ivo.threekidfamily.domain.person.dto.PersonUpsertResponse;
|
import com.spijkerman.ivo.threekidfamily.domain.person.dto.PersonUpsertResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.val;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -19,7 +21,19 @@ public class PersonController {
|
|||||||
public ResponseEntity<List<PersonUpsertResponse>> upsertPerson(
|
public ResponseEntity<List<PersonUpsertResponse>> upsertPerson(
|
||||||
@RequestBody PersonUpsertRequest request
|
@RequestBody PersonUpsertRequest request
|
||||||
) {
|
) {
|
||||||
throw new UnsupportedOperationException();
|
val person = request.toDomain();
|
||||||
|
personService.upsertPerson(person);
|
||||||
|
|
||||||
|
val parentsInValidFamilies = personService.getParentsInValidFamilies();
|
||||||
|
if (parentsInValidFamilies.isEmpty()) {
|
||||||
|
// Specs state HTTP 444, but since this is a custom nginx status + not a client error, I cannot do this
|
||||||
|
return ResponseEntity.noContent().build(); // 204 instead
|
||||||
|
}
|
||||||
|
val result = new ArrayList<PersonUpsertResponse>();
|
||||||
|
for (val parent : parentsInValidFamilies) {
|
||||||
|
result.add(PersonUpsertResponse.fromDomain(parent));
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package com.spijkerman.ivo.threekidfamily.domain.person;
|
|
||||||
|
|
||||||
import jakarta.annotation.Nullable;
|
|
||||||
import jakarta.persistence.ElementCollection;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Entity
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class PersonEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@NonNull
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private LocalDate birthDate;
|
|
||||||
|
|
||||||
// Store only relation IDs instead of JPA mappings to reduce JPA/Hibernate tomfoolery
|
|
||||||
@Nullable
|
|
||||||
private Integer partnerId;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Singular
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
private Set<Integer> childIds = new HashSet<>();
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Singular
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
private Set<Integer> parentIds = new HashSet<>();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,5 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PersonRepository extends JpaRepository<PersonEntity, Integer> {
|
public interface PersonRepository extends JpaRepository<Person, Integer> {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.spijkerman.ivo.threekidfamily.domain.person;
|
package com.spijkerman.ivo.threekidfamily.domain.person;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
import lombok.Locked;
|
import lombok.Locked;
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.val;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.*;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -16,10 +20,142 @@ public class PersonService {
|
|||||||
/**
|
/**
|
||||||
* Upserts a person to storage, overwriting any existing or conflicting data.
|
* Upserts a person to storage, overwriting any existing or conflicting data.
|
||||||
*
|
*
|
||||||
* @param person The Person data to be persisted
|
* @param newPerson The Person data to be persisted
|
||||||
* @return The IDs of the Persons that have been modified because of this operation.
|
|
||||||
*/
|
*/
|
||||||
public @NonNull Set<Integer> upsertPerson(Person person) {
|
@Locked.Write
|
||||||
|
@Transactional
|
||||||
|
public void upsertPerson(Person newPerson) {
|
||||||
|
int id = newPerson.id();
|
||||||
|
val relatedPersons = retrieveRelatedOf(newPerson);
|
||||||
|
val oldPerson = relatedPersons.getLeft();
|
||||||
|
val related = relatedPersons.getRight();
|
||||||
|
|
||||||
|
val modifiedPersons = oldPerson == null ? modifiedPersonSetOf(
|
||||||
|
setPartner(id, related, newPerson.partnerId(), null),
|
||||||
|
setParents(id, related, newPerson.parentIds(), Set.of()),
|
||||||
|
setChildren(id, related, newPerson.childIds(), Set.of())
|
||||||
|
) : modifiedPersonSetOf(
|
||||||
|
setPartner(id, related, newPerson.partnerId(), oldPerson.partnerId()),
|
||||||
|
setParents(id, related, newPerson.parentIds(), oldPerson.parentIds()),
|
||||||
|
setChildren(id, related, newPerson.childIds(), oldPerson.childIds())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Objects.equals(newPerson, oldPerson)) {
|
||||||
|
modifiedPersons.add(newPerson);
|
||||||
|
}
|
||||||
|
if (!modifiedPersons.isEmpty()) {
|
||||||
|
personRepository.saveAll(modifiedPersons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pair<Person, Map<Integer, Person>> retrieveRelatedOf(Person newPerson) {
|
||||||
|
val personsToConsider = Lists.newArrayList(newPerson);
|
||||||
|
val oldPerson = personRepository.findById(newPerson.id());
|
||||||
|
oldPerson.ifPresent(personsToConsider::add);
|
||||||
|
|
||||||
|
val ids = new HashSet<Integer>();
|
||||||
|
for (val person : personsToConsider) {
|
||||||
|
if (person.partnerId() != null) {
|
||||||
|
ids.add(person.partnerId());
|
||||||
|
}
|
||||||
|
ids.addAll(person.parentIds());
|
||||||
|
ids.addAll(person.childIds());
|
||||||
|
}
|
||||||
|
|
||||||
|
val related = new HashMap<Integer, Person>();
|
||||||
|
for (val foundPerson : personRepository.findAllById(ids)) {
|
||||||
|
related.put(foundPerson.id(), foundPerson);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to copy, because .difference returns an unmodifiable view
|
||||||
|
val missingIds = Set.copyOf(Sets.difference(ids, related.keySet()));
|
||||||
|
for (val missingId : missingIds) {
|
||||||
|
related.put(missingId, new Person(missingId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair.of(
|
||||||
|
oldPerson.orElse(null),
|
||||||
|
Collections.unmodifiableMap(related)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Person> modifiedPersonSetOf(
|
||||||
|
Set<Person> modifiedPartners,
|
||||||
|
Set<Person> modifiedParents,
|
||||||
|
Set<Person> modifiedChildren
|
||||||
|
) {
|
||||||
|
val result = new HashSet<Person>();
|
||||||
|
result.addAll(modifiedPartners);
|
||||||
|
result.addAll(modifiedParents);
|
||||||
|
result.addAll(modifiedChildren);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Person> setPartner(
|
||||||
|
int selfId,
|
||||||
|
Map<Integer, Person> related,
|
||||||
|
Integer newPartnerId,
|
||||||
|
Integer oldPartnerId
|
||||||
|
) {
|
||||||
|
val modifiedPersons = new HashSet<Person>();
|
||||||
|
if (Objects.equals(newPartnerId, oldPartnerId)) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
if (newPartnerId != null) {
|
||||||
|
val newPartner = related.get(newPartnerId);
|
||||||
|
newPartner.partnerId(selfId);
|
||||||
|
modifiedPersons.add(newPartner);
|
||||||
|
}
|
||||||
|
if (oldPartnerId != null) {
|
||||||
|
val oldPartner = related.get(oldPartnerId);
|
||||||
|
oldPartner.partnerId(null);
|
||||||
|
modifiedPersons.add(oldPartner);
|
||||||
|
}
|
||||||
|
return modifiedPersons;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Person> setParents(
|
||||||
|
int childId,
|
||||||
|
Map<Integer, Person> related,
|
||||||
|
Set<Integer> newParentIds,
|
||||||
|
Set<Integer> oldParentIds
|
||||||
|
) {
|
||||||
|
val modifiedPersons = new HashSet<Person>();
|
||||||
|
for (val removeFromId : Sets.difference(oldParentIds, newParentIds)) {
|
||||||
|
val removeFrom = related.get(removeFromId);
|
||||||
|
removeFrom.childIds().remove(childId);
|
||||||
|
modifiedPersons.add(removeFrom);
|
||||||
|
}
|
||||||
|
for (val addToId : Sets.difference(newParentIds, oldParentIds)) {
|
||||||
|
val addTo = related.get(addToId);
|
||||||
|
addTo.childIds().add(childId);
|
||||||
|
modifiedPersons.add(addTo);
|
||||||
|
}
|
||||||
|
return modifiedPersons;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Person> setChildren(
|
||||||
|
int parentId,
|
||||||
|
Map<Integer, Person> related,
|
||||||
|
Set<Integer> newChildIds,
|
||||||
|
Set<Integer> oldChildIds
|
||||||
|
) {
|
||||||
|
val modifiedPersons = new HashSet<Person>();
|
||||||
|
for (val removeFromId : Sets.difference(oldChildIds, newChildIds)) {
|
||||||
|
val removeFrom = related.get(removeFromId);
|
||||||
|
removeFrom.parentIds().remove(parentId);
|
||||||
|
modifiedPersons.add(removeFrom);
|
||||||
|
}
|
||||||
|
for (val addToId : Sets.difference(newChildIds, oldChildIds)) {
|
||||||
|
val addTo = related.get(addToId);
|
||||||
|
addTo.parentIds().add(parentId);
|
||||||
|
modifiedPersons.add(addTo);
|
||||||
|
}
|
||||||
|
return modifiedPersons;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Locked.Read
|
||||||
|
public SortedSet<Person> getParentsInValidFamilies() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,14 +165,17 @@ public class PersonService {
|
|||||||
* @param id The ID of the person to be retrieved.
|
* @param id The ID of the person to be retrieved.
|
||||||
* @return A Person object, may contain all information, or just the ID.
|
* @return A Person object, may contain all information, or just the ID.
|
||||||
*/
|
*/
|
||||||
|
@Locked.Read
|
||||||
public Person getPersonById(int id) {
|
public Person getPersonById(int id) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all data related to the specified user, and blacklists that ID from later use.
|
* Deletes all data related to the specified user, and blacklists that ID from later use.
|
||||||
|
*
|
||||||
* @param id The ID of the person to be deleted and blacklisted.
|
* @param id The ID of the person to be deleted and blacklisted.
|
||||||
*/
|
*/
|
||||||
|
@Locked.Write
|
||||||
public void deletePersonById(int id) {
|
public void deletePersonById(int id) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,42 @@
|
|||||||
package com.spijkerman.ivo.threekidfamily.domain.person.dto;
|
package com.spijkerman.ivo.threekidfamily.domain.person.dto;
|
||||||
|
|
||||||
public record PersonUpsertRequest() {
|
import com.spijkerman.ivo.threekidfamily.domain.person.Person;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.val;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record PersonUpsertRequest(
|
||||||
|
int id,
|
||||||
|
@Nullable String name,
|
||||||
|
@Nullable LocalDate birthDate,
|
||||||
|
@Nullable PersonReference parent1,
|
||||||
|
@Nullable PersonReference parent2,
|
||||||
|
@Nullable PersonReference partner,
|
||||||
|
@Nullable List<PersonReference> children
|
||||||
|
) {
|
||||||
|
public record PersonReference(int id){}
|
||||||
|
|
||||||
|
public Person toDomain() {
|
||||||
|
var builder = Person.builder()
|
||||||
|
.id(id)
|
||||||
|
.name(name)
|
||||||
|
.parentId(idOf(parent1))
|
||||||
|
.parentId(idOf(parent2))
|
||||||
|
.partnerId(idOf(partner));
|
||||||
|
if (children != null) {
|
||||||
|
for (val child : children) {
|
||||||
|
builder = builder.childId(idOf(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable Integer idOf(@Nullable PersonReference personReference) {
|
||||||
|
if (personReference == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return personReference.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
package com.spijkerman.ivo.threekidfamily.domain.person.dto;
|
package com.spijkerman.ivo.threekidfamily.domain.person.dto;
|
||||||
|
|
||||||
public record PersonUpsertResponse() {
|
import com.spijkerman.ivo.threekidfamily.domain.person.Person;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public record PersonUpsertResponse(
|
||||||
|
int id,
|
||||||
|
@Nullable String name,
|
||||||
|
@Nullable LocalDate birthDate
|
||||||
|
// Not sure if relations should be returned as well, decided to not
|
||||||
|
) {
|
||||||
|
public static PersonUpsertResponse fromDomain(Person domain) {
|
||||||
|
return new PersonUpsertResponse(domain.id(), domain.name(), domain.birthDate());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.spijkerman.ivo.threekidfamily;
|
package com.spijkerman.ivo.threekidfamily;
|
||||||
|
|
||||||
import com.spijkerman.ivo.threekidfamily.domain.person.Person;
|
import com.spijkerman.ivo.threekidfamily.domain.person.Person;
|
||||||
import com.spijkerman.ivo.threekidfamily.domain.person.PersonEntity;
|
|
||||||
import com.spijkerman.ivo.threekidfamily.domain.person.PersonRepository;
|
import com.spijkerman.ivo.threekidfamily.domain.person.PersonRepository;
|
||||||
import com.spijkerman.ivo.threekidfamily.domain.person.PersonService;
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -41,7 +39,7 @@ class ThreeKidFamilyIT {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDbRoundTrip() {
|
void testDbRoundTrip() {
|
||||||
val given = PersonEntity.builder().id(12).build();
|
val given = Person.builder().id(12).build();
|
||||||
val saved = personRepository.save(given);
|
val saved = personRepository.save(given);
|
||||||
|
|
||||||
assertThat(saved).isEqualTo(given);
|
assertThat(saved).isEqualTo(given);
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package com.spijkerman.ivo.threekidfamily.domain.person;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import lombok.val;
|
||||||
|
import org.assertj.core.util.Sets;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
class PersonServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpsertPerson_forFullPerson_verifyFamilySaved() {
|
||||||
|
val repository = repository();
|
||||||
|
val sut = new PersonService(repository);
|
||||||
|
|
||||||
|
val given = Person.builder()
|
||||||
|
.id(1)
|
||||||
|
.name("Alice")
|
||||||
|
.birthDate(LocalDate.now())
|
||||||
|
.parentId(2)
|
||||||
|
.childId(10)
|
||||||
|
.partnerId(20)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sut.upsertPerson(given);
|
||||||
|
|
||||||
|
verify(repository).findById(1);
|
||||||
|
verify(repository).findAllById(Set.of(2, 10, 20));
|
||||||
|
verify(repository).saveAll(Set.of(
|
||||||
|
given, // Alice
|
||||||
|
Person.builder().id(2).childId(1).build(), // Parent
|
||||||
|
Person.builder().id(10).parentId(1).build(), // Child
|
||||||
|
Person.builder().id(20).partnerId(1).build() // Partner
|
||||||
|
));
|
||||||
|
verifyNoMoreInteractions(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpsertPerson_forFullThenSlimPerson_expectFamilyStripped() {
|
||||||
|
val repository = repository();
|
||||||
|
val sut = new PersonService(repository);
|
||||||
|
|
||||||
|
val fullGiven = Person.builder()
|
||||||
|
.id(1)
|
||||||
|
.name("Alice")
|
||||||
|
.birthDate(LocalDate.now())
|
||||||
|
.parentId(2)
|
||||||
|
.childId(10)
|
||||||
|
.partnerId(20)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
val slimGiven = Person.builder()
|
||||||
|
.id(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sut.upsertPerson(fullGiven);
|
||||||
|
clearInvocations(repository); // So we only have to verify the invocations of inserting slim alice
|
||||||
|
sut.upsertPerson(slimGiven);
|
||||||
|
|
||||||
|
verify(repository).findById(1);
|
||||||
|
verify(repository).findAllById(Set.of(2, 10, 20));
|
||||||
|
verify(repository).saveAll(Set.of(
|
||||||
|
slimGiven,
|
||||||
|
Person.builder().id(2).build(), // Parent
|
||||||
|
Person.builder().id(10).build(), // Child
|
||||||
|
Person.builder().id(20).build() // Partner
|
||||||
|
));
|
||||||
|
verifyNoMoreInteractions(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpsertPerson_forTwoPartners_expectOneSave() {
|
||||||
|
val repository = repository();
|
||||||
|
val sut = new PersonService(repository);
|
||||||
|
|
||||||
|
val alice = Person.builder()
|
||||||
|
.id(1)
|
||||||
|
.partnerId(2)
|
||||||
|
.build();
|
||||||
|
val bob = Person.builder()
|
||||||
|
.id(2)
|
||||||
|
.partnerId(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sut.upsertPerson(alice);
|
||||||
|
sut.upsertPerson(bob);
|
||||||
|
|
||||||
|
verify(repository).findById(1);
|
||||||
|
verify(repository).findById(2);
|
||||||
|
verify(repository).findAllById(Set.of(1));
|
||||||
|
verify(repository).findAllById(Set.of(2));
|
||||||
|
verify(repository).saveAll(Set.of(alice, bob));
|
||||||
|
verifyNoMoreInteractions(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PersonRepository repository() {
|
||||||
|
val mockDb = new HashMap<Integer, Person>();
|
||||||
|
val repository = mock(PersonRepository.class);
|
||||||
|
|
||||||
|
when(repository.save(any())).then(inv -> {
|
||||||
|
Person person = inv.getArgument(0);
|
||||||
|
mockDb.put(person.id(), person);
|
||||||
|
return person;
|
||||||
|
});
|
||||||
|
|
||||||
|
when(repository.saveAll(any())).then(inv -> {
|
||||||
|
Iterable<Person> persons = inv.getArgument(0);
|
||||||
|
for (val person : persons) {
|
||||||
|
mockDb.put(person.id(), person);
|
||||||
|
}
|
||||||
|
return Lists.newArrayList(persons);
|
||||||
|
});
|
||||||
|
|
||||||
|
when(repository.findById(any())).then(inv -> {
|
||||||
|
int id = inv.getArgument(0);
|
||||||
|
return Optional.ofNullable(mockDb.get(id));
|
||||||
|
});
|
||||||
|
|
||||||
|
when(repository.findAllById(any())).then(inv -> {
|
||||||
|
val ids = Sets.<Integer>newHashSet(inv.getArgument(0));
|
||||||
|
return List.copyOf(Maps.filterKeys(mockDb, ids::contains).values());
|
||||||
|
});
|
||||||
|
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user