From 2420453daaf68c4fae23a9416775a8aeca368bed Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:46:27 -0600 Subject: [PATCH] enable Hibernate validation --- .../backend/RuntimeClasspathValidator.java | 2 +- .../dto/appointment/AppointmentRequest.java | 17 +- .../dto/appointment/AppointmentResponse.java | 35 +- .../petshop/backend/entity/Appointment.java | 22 +- backend/src/main/resources/application.yml | 4 +- .../controller/DropdownControllerTest.java | 201 ----------- .../backend/service/AdoptionServiceTest.java | 150 -------- .../service/AppointmentServiceTest.java | 332 ------------------ .../backend/service/ChatServiceTest.java | 199 ----------- .../backend/service/PetServiceTest.java | 173 --------- 10 files changed, 36 insertions(+), 1099 deletions(-) delete mode 100644 backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/PetServiceTest.java diff --git a/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java b/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java index ad18d198..e123f66a 100644 --- a/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java +++ b/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java @@ -11,7 +11,7 @@ final class RuntimeClasspathValidator { if (!resourceExists("application.yml")) { throw new IllegalStateException("Backend resources are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration."); } - if (!resourceExists("db/migration/V1__baseline_schema.sql")) { + if (!resourceExists("db/migration/V1__target_baseline.sql")) { throw new IllegalStateException("Flyway migration files are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration."); } } diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java index c60c3fcd..9ddb9ad2 100644 --- a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -3,7 +3,6 @@ package com.petshop.backend.dto.appointment; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.time.LocalTime; -import java.util.List; import java.util.Objects; public class AppointmentRequest { @@ -25,7 +24,7 @@ public class AppointmentRequest { @NotNull(message = "Appointment status is required") private String appointmentStatus; - private List petIds; + private Long petId; private Long employeeId; @@ -77,12 +76,12 @@ public class AppointmentRequest { this.appointmentStatus = appointmentStatus; } - public List getPetIds() { - return petIds; + public Long getPetId() { + return petId; } - public void setPetIds(List petIds) { - this.petIds = petIds; + public void setPetId(Long petId) { + this.petId = petId; } public Long getEmployeeId() { @@ -104,13 +103,13 @@ public class AppointmentRequest { Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && - Objects.equals(petIds, that.petIds) && + Objects.equals(petId, that.petId) && Objects.equals(employeeId, that.employeeId); } @Override public int hashCode() { - return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds, employeeId); + return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petId, employeeId); } @Override @@ -122,7 +121,7 @@ public class AppointmentRequest { ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + ", appointmentStatus='" + appointmentStatus + '\'' + - ", petIds=" + petIds + + ", petId=" + petId + ", employeeId=" + employeeId + '}'; } diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java index f8e14ac2..655a788c 100644 --- a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -3,7 +3,6 @@ package com.petshop.backend.dto.appointment; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.List; import java.util.Objects; public class AppointmentResponse { @@ -19,15 +18,15 @@ public class AppointmentResponse { private String appointmentStatus; private Long employeeId; private String employeeName; - private List petNames; - private List petIds; + private String petName; + private Long petId; private LocalDateTime createdAt; private LocalDateTime updatedAt; public AppointmentResponse() { } - public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long storeId, String storeName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, List petNames, List petIds, LocalDateTime createdAt, LocalDateTime updatedAt) { + public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long storeId, String storeName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, String petName, Long petId, LocalDateTime createdAt, LocalDateTime updatedAt) { this.appointmentId = appointmentId; this.customerId = customerId; this.customerName = customerName; @@ -38,8 +37,8 @@ public class AppointmentResponse { this.appointmentDate = appointmentDate; this.appointmentTime = appointmentTime; this.appointmentStatus = appointmentStatus; - this.petNames = petNames; - this.petIds = petIds; + this.petName = petName; + this.petId = petId; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -140,20 +139,20 @@ public class AppointmentResponse { this.employeeName = employeeName; } - public List getPetNames() { - return petNames; + public String getPetName() { + return petName; } - public void setPetNames(List petNames) { - this.petNames = petNames; + public void setPetName(String petName) { + this.petName = petName; } - public List getPetIds() { - return petIds; + public Long getPetId() { + return petId; } - public void setPetIds(List petIds) { - this.petIds = petIds; + public void setPetId(Long petId) { + this.petId = petId; } public LocalDateTime getCreatedAt() { @@ -177,12 +176,12 @@ public class AppointmentResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AppointmentResponse that = (AppointmentResponse) o; - return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petName, that.petName) && Objects.equals(petId, that.petId) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(appointmentId, customerId, customerName, storeId, storeName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt); + return Objects.hash(appointmentId, customerId, customerName, storeId, storeName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petName, petId, createdAt, updatedAt); } @Override @@ -198,8 +197,8 @@ public class AppointmentResponse { ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + ", appointmentStatus='" + appointmentStatus + '\'' + - ", petNames=" + petNames + - ", petIds=" + petIds + + ", petName='" + petName + '\'' + + ", petId=" + petId + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/backend/src/main/java/com/petshop/backend/entity/Appointment.java b/backend/src/main/java/com/petshop/backend/entity/Appointment.java index f313d928..0e80f58e 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/backend/src/main/java/com/petshop/backend/entity/Appointment.java @@ -7,9 +7,7 @@ import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; @Entity @Table(name = "appointment") @@ -44,13 +42,9 @@ public class Appointment { @Column(nullable = false, length = 20) private String appointmentStatus; - @ManyToMany - @JoinTable( - name = "appointmentPet", - joinColumns = @JoinColumn(name = "appointmentId"), - inverseJoinColumns = @JoinColumn(name = "petId") - ) - private Set pets = new HashSet<>(); + @ManyToOne + @JoinColumn(name = "petId") + private Pet pet; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -127,12 +121,12 @@ public class Appointment { this.appointmentStatus = appointmentStatus; } - public Set getPets() { - return pets; + public Pet getPet() { + return pet; } - public void setPets(Set pets) { - this.pets = pets; + public void setPet(Pet pet) { + this.pet = pet; } public LocalDateTime getCreatedAt() { @@ -175,7 +169,7 @@ public class Appointment { ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + ", appointmentStatus='" + appointmentStatus + '\'' + - ", pets=" + pets + + ", pet=" + pet + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 98aac596..5c79d7a6 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -20,7 +20,7 @@ spring: jpa: hibernate: - ddl-auto: none + ddl-auto: validate naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} @@ -32,7 +32,7 @@ spring: flyway: enabled: true baseline-on-migrate: true - baseline-version: 0 + baseline-version: 1 server: port: ${SERVER_PORT:8080} diff --git a/backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java b/backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java deleted file mode 100644 index e7caa86e..00000000 --- a/backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.petshop.backend.controller; - -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.EmployeeStore; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.StoreLocation; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.CategoryRepository; -import com.petshop.backend.repository.CustomerPetRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeStoreRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.ProductRepository; -import com.petshop.backend.repository.ServiceRepository; -import com.petshop.backend.repository.StoreRepository; -import com.petshop.backend.repository.SupplierRepository; -import com.petshop.backend.repository.UserRepository; -import com.petshop.backend.security.AppPrincipal; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class DropdownControllerTest { - - private PetRepository petRepository; - private CustomerRepository customerRepository; - private CustomerPetRepository customerPetRepository; - private ServiceRepository serviceRepository; - private ProductRepository productRepository; - private CategoryRepository categoryRepository; - private StoreRepository storeRepository; - private SupplierRepository supplierRepository; - private EmployeeStoreRepository employeeStoreRepository; - private UserRepository userRepository; - private DropdownController controller; - - @BeforeEach - void setUp() { - petRepository = mock(PetRepository.class); - customerRepository = mock(CustomerRepository.class); - customerPetRepository = mock(CustomerPetRepository.class); - serviceRepository = mock(ServiceRepository.class); - productRepository = mock(ProductRepository.class); - categoryRepository = mock(CategoryRepository.class); - storeRepository = mock(StoreRepository.class); - supplierRepository = mock(SupplierRepository.class); - employeeStoreRepository = mock(EmployeeStoreRepository.class); - userRepository = mock(UserRepository.class); - - controller = new DropdownController( - petRepository, - customerRepository, - customerPetRepository, - serviceRepository, - productRepository, - categoryRepository, - storeRepository, - supplierRepository, - employeeStoreRepository, - userRepository - ); - } - - @AfterEach - void tearDown() { - SecurityContextHolder.clearContext(); - } - - private void setAuthentication(Long userId, User.Role role) { - SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken( - new AppPrincipal(userId, "user", role, 0), - null, - List.of() - ) - ); - } - - @Test - void getStoreEmployeesReturnsOnlyStaffLinkedEmployees() { - StoreLocation store = new StoreLocation(); - store.setStoreId(1L); - - Employee staffEmployee = new Employee(); - staffEmployee.setEmployeeId(7L); - staffEmployee.setUserId(7L); - staffEmployee.setFirstName("Alex"); - staffEmployee.setLastName("Jones"); - staffEmployee.setIsActive(true); - - Employee adminEmployee = new Employee(); - adminEmployee.setEmployeeId(8L); - adminEmployee.setUserId(8L); - adminEmployee.setFirstName("Admin"); - adminEmployee.setLastName("Helper"); - adminEmployee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - - User adminUser = new User(); - adminUser.setId(8L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(staffEmployee, store), new EmployeeStore(adminEmployee, store))); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - when(userRepository.findById(8L)).thenReturn(Optional.of(adminUser)); - - var response = controller.getStoreEmployees(1L); - - assertEquals(1, response.getBody().size()); - assertEquals(Long.valueOf(7L), response.getBody().get(0).getId()); - } - - @Test - void getStoreEmployeesReturnsAllStaffWhenStoreIdIsNull() { - StoreLocation store = new StoreLocation(); - store.setStoreId(1L); - - Employee staffEmployee = new Employee(); - staffEmployee.setEmployeeId(7L); - staffEmployee.setUserId(7L); - staffEmployee.setFirstName("Alex"); - staffEmployee.setLastName("Jones"); - staffEmployee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - - when(employeeStoreRepository.findActiveAllOrderByEmployeeEmployeeIdAsc()) - .thenReturn(List.of(new EmployeeStore(staffEmployee, store))); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - - var response = controller.getStoreEmployees(null); - - assertEquals(1, response.getBody().size()); - assertEquals(Long.valueOf(7L), response.getBody().get(0).getId()); - } - - @Test - void getStoreEmployeesExcludesInactiveStaffUsers() { - StoreLocation store = new StoreLocation(); - store.setStoreId(1L); - - Employee inactiveStaffEmployee = new Employee(); - inactiveStaffEmployee.setEmployeeId(7L); - inactiveStaffEmployee.setUserId(7L); - inactiveStaffEmployee.setFirstName("Alex"); - inactiveStaffEmployee.setLastName("Jones"); - inactiveStaffEmployee.setIsActive(true); - - User inactiveStaffUser = new User(); - inactiveStaffUser.setId(7L); - inactiveStaffUser.setRole(User.Role.STAFF); - inactiveStaffUser.setActive(false); - - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(inactiveStaffEmployee, store))); - when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser)); - - var response = controller.getStoreEmployees(1L); - - assertEquals(0, response.getBody().size()); - } - - @Test - void getAppointmentCustomersReturnsOnlyCustomersWithPetsForStaff() { - User staffUser = new User(); - staffUser.setId(99L); - staffUser.setRole(User.Role.STAFF); - when(userRepository.findById(99L)).thenReturn(Optional.of(staffUser)); - setAuthentication(99L, User.Role.STAFF); - - Customer one = new Customer(); - one.setCustomerId(1L); - one.setFirstName("Alex"); - one.setLastName("Brown"); - - when(customerRepository.findAllWithPets()).thenReturn(List.of(one)); - - var response = controller.getAppointmentCustomers(); - - assertEquals(1, response.getBody().size()); - assertEquals(Long.valueOf(1L), response.getBody().get(0).getId()); - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java b/backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java deleted file mode 100644 index a133c29f..00000000 --- a/backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.adoption.AdoptionRequest; -import com.petshop.backend.entity.Adoption; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.Pet; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.AdoptionRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.UserRepository; -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 org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.quality.Strictness; - -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class AdoptionServiceTest { - - @Mock private AdoptionRepository adoptionRepository; - @Mock private PetRepository petRepository; - @Mock private CustomerRepository customerRepository; - @Mock private EmployeeRepository employeeRepository; - @Mock private UserRepository userRepository; - - @InjectMocks - private AdoptionService adoptionService; - - private Pet pet; - private Customer customer; - private Employee staffEmployee; - private Employee adminEmployee; - - @BeforeEach - void setUp() { - pet = new Pet(); - pet.setPetId(1L); - pet.setPetName("Buddy"); - pet.setPetStatus("Available"); - - customer = new Customer(); - customer.setCustomerId(1L); - customer.setFirstName("Pat"); - customer.setLastName("Owner"); - - staffEmployee = new Employee(); - staffEmployee.setEmployeeId(7L); - staffEmployee.setUserId(7L); - staffEmployee.setFirstName("Alex"); - staffEmployee.setLastName("Jones"); - staffEmployee.setIsActive(true); - - adminEmployee = new Employee(); - adminEmployee.setEmployeeId(8L); - adminEmployee.setUserId(8L); - adminEmployee.setFirstName("Admin"); - adminEmployee.setLastName("Helper"); - adminEmployee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - - User adminUser = new User(); - adminUser.setId(8L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(8L)).thenReturn(Optional.of(adminUser)); - } - - @Test - void createAdoptionAutoAssignsFirstStaffEmployee() { - when(petRepository.findById(1L)).thenReturn(Optional.of(pet)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - - when(employeeRepository.findAllByIsActiveTrueOrderByEmployeeIdAsc()).thenReturn(List.of(adminEmployee, staffEmployee)); - when(adoptionRepository.save(any(Adoption.class))).thenAnswer(invocation -> { - Adoption adoption = invocation.getArgument(0); - adoption.setAdoptionId(10L); - return adoption; - }); - - AdoptionRequest request = new AdoptionRequest(); - request.setPetId(1L); - request.setCustomerId(1L); - request.setAdoptionDate(LocalDate.now()); - request.setAdoptionStatus("Pending"); - - var response = adoptionService.createAdoption(request); - - assertEquals(7L, response.getEmployeeId()); - assertEquals("Alex Jones", response.getEmployeeName()); - } - - @Test - void createAdoptionRejectsAdminEmployeeSelection() { - when(petRepository.findById(1L)).thenReturn(Optional.of(pet)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(employeeRepository.findById(8L)).thenReturn(Optional.of(adminEmployee)); - - AdoptionRequest request = new AdoptionRequest(); - request.setPetId(1L); - request.setCustomerId(1L); - request.setEmployeeId(8L); - request.setAdoptionDate(LocalDate.now()); - request.setAdoptionStatus("Pending"); - - assertThrows(IllegalArgumentException.class, () -> adoptionService.createAdoption(request)); - } - - @Test - void createAdoptionRejectsInactiveStaffUserSelection() { - User inactiveStaffUser = new User(); - inactiveStaffUser.setId(7L); - inactiveStaffUser.setRole(User.Role.STAFF); - inactiveStaffUser.setActive(false); - when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser)); - - when(petRepository.findById(1L)).thenReturn(Optional.of(pet)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(employeeRepository.findById(7L)).thenReturn(Optional.of(staffEmployee)); - - AdoptionRequest request = new AdoptionRequest(); - request.setPetId(1L); - request.setCustomerId(1L); - request.setEmployeeId(7L); - request.setAdoptionDate(LocalDate.now()); - request.setAdoptionStatus("Pending"); - - assertThrows(IllegalArgumentException.class, () -> adoptionService.createAdoption(request)); - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java b/backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java deleted file mode 100644 index 3e6f2d89..00000000 --- a/backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java +++ /dev/null @@ -1,332 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.entity.Appointment; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.CustomerPet; -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.EmployeeStore; -import com.petshop.backend.entity.Pet; -import com.petshop.backend.entity.Service; -import com.petshop.backend.entity.StoreLocation; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.AppointmentRepository; -import com.petshop.backend.repository.CustomerPetRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; -import com.petshop.backend.repository.EmployeeStoreRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.ServiceRepository; -import com.petshop.backend.repository.StoreRepository; -import com.petshop.backend.repository.UserRepository; -import com.petshop.backend.security.AppPrincipal; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.quality.Strictness; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class AppointmentServiceTest { - - @Mock private AppointmentRepository appointmentRepository; - @Mock private CustomerRepository customerRepository; - @Mock private CustomerPetRepository customerPetRepository; - @Mock private ServiceRepository serviceRepository; - @Mock private PetRepository petRepository; - @Mock private StoreRepository storeRepository; - @Mock private UserRepository userRepository; - @Mock private EmployeeRepository employeeRepository; - @Mock private EmployeeStoreRepository employeeStoreRepository; - - @InjectMocks - private AppointmentService appointmentService; - - private Customer customer; - private StoreLocation store; - private Service grooming; - private Service nailTrim; - private Pet pet; - private CustomerPet customerPet; - private Employee employee; - private LocalDate date; - - @BeforeEach - void setUp() { - setAuthentication(99L, User.Role.ADMIN); - User adminUser = new User(); - adminUser.setId(99L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser)); - - customer = new Customer(); - customer.setCustomerId(1L); - customer.setFirstName("Pat"); - customer.setLastName("Owner"); - - store = new StoreLocation(); - store.setStoreId(1L); - store.setStoreName("Main Store"); - - grooming = new Service(); - grooming.setServiceId(1L); - grooming.setServiceName("Grooming"); - grooming.setServiceDuration(30); - - nailTrim = new Service(); - nailTrim.setServiceId(2L); - nailTrim.setServiceName("Nail Trim"); - nailTrim.setServiceDuration(30); - - pet = new Pet(); - pet.setPetId(1L); - pet.setPetName("Milo"); - - customerPet = new CustomerPet(); - customerPet.setCustomerPetId(11L); - customerPet.setPetName("Milo Jr"); - customerPet.setCustomer(customer); - - employee = new Employee(); - employee.setEmployeeId(7L); - employee.setUserId(7L); - employee.setFirstName("Alex"); - employee.setLastName("Jones"); - employee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - - date = LocalDate.now().plusDays(1); - } - - @AfterEach - void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - void checkAvailabilityAllowsConcurrentAppointmentsIfAnotherEmployeeFree() { - Employee employee2 = new Employee(); - employee2.setEmployeeId(8L); - employee2.setUserId(8L); - employee2.setFirstName("Bob"); - employee2.setIsActive(true); - - User staffUser2 = new User(); - staffUser2.setId(8L); - staffUser2.setRole(User.Role.STAFF); - staffUser2.setActive(true); - when(userRepository.findById(8L)).thenReturn(Optional.of(staffUser2)); - - Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(2L)).thenReturn(Optional.of(nailTrim)); - - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store), new EmployeeStore(employee2, store))); - - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of(existing)); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(8L, date)).thenReturn(List.of()); - - List slots = appointmentService.checkAvailability(1L, 2L, date); - - assertTrue(slots.contains("10:00")); - } - - @Test - void createAppointmentRejectsCustomerPetOwnedByDifferentCustomerForStaff() { - setAuthentication(7L, User.Role.STAFF); - when(employeeRepository.findByUserId(7L)).thenReturn(Optional.of(employee)); - when(employeeStoreRepository.findByEmployeeEmployeeId(7L)).thenReturn(Optional.of(new EmployeeStore(employee, store))); - - Customer otherCustomer = new Customer(); - otherCustomer.setCustomerId(2L); - - CustomerPet otherCustomerPet = new CustomerPet(); - otherCustomerPet.setCustomerPetId(22L); - otherCustomerPet.setCustomer(otherCustomer); - otherCustomerPet.setPetName("Not Yours"); - - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store))); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet)); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(22L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - @Test - void createAppointmentRejectsCustomerPetOwnedByDifferentCustomer() { - setAuthentication(99L, User.Role.ADMIN); - - Customer otherCustomer = new Customer(); - otherCustomer.setCustomerId(2L); - - CustomerPet otherCustomerPet = new CustomerPet(); - otherCustomerPet.setCustomerPetId(22L); - otherCustomerPet.setCustomer(otherCustomer); - otherCustomerPet.setPetName("Not Yours"); - - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store))); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet)); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(22L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - @Test - void createAppointmentRejectsAdminEmployeeSelection() { - setAuthentication(99L, User.Role.ADMIN); - User adminUser = new User(); - adminUser.setId(99L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser)); - - Employee adminEmployee = new Employee(); - adminEmployee.setEmployeeId(8L); - adminEmployee.setUserId(8L); - adminEmployee.setFirstName("Admin"); - adminEmployee.setLastName("Helper"); - adminEmployee.setIsActive(true); - - User adminLinkedUser = new User(); - adminLinkedUser.setId(8L); - adminLinkedUser.setRole(User.Role.ADMIN); - adminLinkedUser.setActive(true); - - when(userRepository.findById(8L)).thenReturn(Optional.of(adminLinkedUser)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeRepository.findById(8L)).thenReturn(Optional.of(adminEmployee)); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(adminEmployee, store), new EmployeeStore(employee, store))); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setEmployeeId(8L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(11L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - @Test - void createAppointmentRejectsInactiveStaffUserSelection() { - setAuthentication(99L, User.Role.ADMIN); - User adminUser = new User(); - adminUser.setId(99L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser)); - - User inactiveStaffUser = new User(); - inactiveStaffUser.setId(7L); - inactiveStaffUser.setRole(User.Role.STAFF); - inactiveStaffUser.setActive(false); - when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser)); - - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeRepository.findById(7L)).thenReturn(Optional.of(employee)); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store))); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setEmployeeId(7L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(11L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - private Appointment appointment(Long id, LocalDate date, LocalTime time, Service service, StoreLocation storeLocation) { - Appointment appointment = new Appointment(); - appointment.setAppointmentId(id); - appointment.setAppointmentDate(date); - appointment.setAppointmentTime(time); - appointment.setAppointmentStatus("Booked"); - appointment.setService(service); - appointment.setStore(storeLocation); - appointment.setEmployee(employee); - appointment.setCustomer(customer); - appointment.setPets(Set.of()); - appointment.setCustomerPets(Set.of()); - return appointment; - } - - private void setAuthentication(Long userId, User.Role role) { - SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken( - new AppPrincipal(userId, "user", role, 0), - "n/a", - List.of(new SimpleGrantedAuthority("ROLE_" + role.name())) - ) - ); - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java b/backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java deleted file mode 100644 index 4ce13bd2..00000000 --- a/backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.chat.MessageRequest; -import com.petshop.backend.dto.chat.UpdateConversationRequest; -import com.petshop.backend.entity.Conversation; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Message; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.ConversationRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.MessageRepository; -import com.petshop.backend.repository.UserRepository; -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 org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.access.AccessDeniedException; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class ChatServiceTest { - - @Mock - private ConversationRepository conversationRepository; - - @Mock - private MessageRepository messageRepository; - - @Mock - private UserRepository userRepository; - - @Mock - private CustomerRepository customerRepository; - - @InjectMocks - private ChatService chatService; - - private Customer customer; - - @BeforeEach - void setUp() { - customer = new Customer(); - customer.setCustomerId(1L); - customer.setUserId(10L); - customer.setFirstName("Pat"); - customer.setLastName("Owner"); - customer.setEmail("pat@example.com"); - } - - @Test - void updateConversationMarksConversationClosed() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)) - .thenReturn(List.of(message("hello"))); - - var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - assertEquals("hello", response.getLastMessage()); - verify(conversationRepository).save(conversation); - } - - @Test - void updateConversationRejectsOtherCustomer() { - Conversation conversation = conversation(99L, 2L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - - assertThrows(AccessDeniedException.class, - () -> chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED"))); - } - - @Test - void updateConversationIsIdempotent() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void staffCanCloseAssignedConversation() { - Conversation conversation = conversation(99L, 1L, 77L, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 77L, User.Role.STAFF, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void staffCanCloseUnassignedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 77L, User.Role.STAFF, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void adminCanCloseAnyConversation() { - Conversation conversation = conversation(99L, 2L, 88L, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 1L, User.Role.ADMIN, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void updateConversationCanReopenClosedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("OPEN")); - - assertEquals("OPEN", response.getStatus()); - } - - @Test - void updateConversationRejectsInvalidStatus() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - - assertThrows(IllegalArgumentException.class, - () -> chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("INVALID"))); - } - - @Test - void sendMessageRejectsClosedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - - assertThrows(AccessDeniedException.class, - () -> chatService.sendMessage(99L, 10L, User.Role.CUSTOMER, new MessageRequest("hello"))); - - verify(messageRepository, never()).save(any()); - } - - @Test - void requestHumanTakeoverRejectsClosedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - - assertThrows(AccessDeniedException.class, - () -> chatService.requestHumanTakeover(99L, 10L, User.Role.CUSTOMER)); - } - - private Conversation conversation(Long id, Long customerId, Long staffId, Conversation.ConversationStatus status) { - Conversation conversation = new Conversation(); - conversation.setId(id); - conversation.setCustomerId(customerId); - conversation.setStaffId(staffId); - conversation.setStatus(status); - conversation.setMode(Conversation.ConversationMode.AUTOMATED); - conversation.setHumanRequestedAt(LocalDateTime.now()); - return conversation; - } - - private Message message(String content) { - Message message = new Message(); - message.setConversationId(99L); - message.setSenderId(10L); - message.setContent(content); - message.setIsRead(false); - return message; - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/PetServiceTest.java b/backend/src/test/java/com/petshop/backend/service/PetServiceTest.java deleted file mode 100644 index 6a80e9ca..00000000 --- a/backend/src/test/java/com/petshop/backend/service/PetServiceTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.entity.Adoption; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Pet; -import com.petshop.backend.entity.User; -import com.petshop.backend.exception.ResourceNotFoundException; -import com.petshop.backend.repository.AdoptionRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.StoreRepository; -import com.petshop.backend.security.AppPrincipal; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PetServiceTest { - - @Mock - private PetRepository petRepository; - - @Mock - private AdoptionRepository adoptionRepository; - - @Mock - private CustomerRepository customerRepository; - - @Mock - private StoreRepository storeRepository; - - @Mock - private CatalogImageStorageService catalogImageStorageService; - - @InjectMocks - private PetService petService; - - @AfterEach - void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - void getAllPetsAnonymousReturnsOnlyPublicPets() { - Pageable pageable = PageRequest.of(0, 10); - Pet availablePet = pet(1L, "Buddy", "Available"); - when(petRepository.searchPublicPets(null, null, null, pageable)).thenReturn(new PageImpl<>(List.of(availablePet), pageable, 1)); - - var result = petService.getAllPets(null, null, null, null, pageable); - - assertEquals(1, result.getTotalElements()); - assertEquals("Buddy", result.getContent().get(0).getPetName()); - verify(petRepository).searchPublicPets(null, null, null, pageable); - verify(petRepository, never()).searchPets(null, null, null, null, pageable); - } - - @Test - void getAllPetsAnonymousWithAdoptedStatusReturnsEmptyPage() { - Pageable pageable = PageRequest.of(0, 10); - - var result = petService.getAllPets(null, null, "Adopted", null, pageable); - - assertEquals(0, result.getTotalElements()); - verify(petRepository, never()).searchPublicPets(null, null, null, pageable); - } - - @Test - void getAllPetsCustomerReturnsVisiblePetsOnly() { - Pageable pageable = PageRequest.of(0, 10); - setAuthentication(25L, User.Role.CUSTOMER); - Pet availablePet = pet(1L, "Buddy", "Available"); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.searchCustomerVisiblePets(25L, null, null, null, pageable)) - .thenReturn(new PageImpl<>(List.of(availablePet, adoptedPet), pageable, 2)); - - var result = petService.getAllPets(null, null, null, null, pageable); - - assertEquals(2, result.getTotalElements()); - verify(petRepository).searchCustomerVisiblePets(25L, null, null, null, pageable); - } - - @Test - void getAllPetsAdminReturnsAllPets() { - Pageable pageable = PageRequest.of(0, 10); - setAuthentication(99L, User.Role.ADMIN); - Pet availablePet = pet(1L, "Buddy", "Available"); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.searchPets(null, null, null, null, pageable)) - .thenReturn(new PageImpl<>(List.of(availablePet, adoptedPet), pageable, 2)); - - var result = petService.getAllPets(null, null, null, null, pageable); - - assertEquals(2, result.getTotalElements()); - verify(petRepository).searchPets(null, null, null, null, pageable); - } - - @Test - void getPetByIdHidesAdoptedPetFromUnrelatedCustomer() { - setAuthentication(50L, User.Role.CUSTOMER); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.findById(2L)).thenReturn(Optional.of(adoptedPet)); - when(adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(2L, "Completed")) - .thenReturn(Optional.of(adoption(2L, 25L))); - - assertThrows(ResourceNotFoundException.class, () -> petService.getPetById(2L)); - } - - @Test - void getPetByIdAllowsOwnerToSeeAdoptedPet() { - setAuthentication(25L, User.Role.CUSTOMER); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.findById(2L)).thenReturn(Optional.of(adoptedPet)); - when(adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(2L, "Completed")) - .thenReturn(Optional.of(adoption(2L, 25L))); - - var result = petService.getPetById(2L); - - assertEquals(2L, result.getPetId()); - } - - private void setAuthentication(Long userId, User.Role role) { - SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken( - new AppPrincipal(userId, "user", role, 0), - "n/a", - List.of(new SimpleGrantedAuthority("ROLE_" + role.name())) - ) - ); - } - - private Pet pet(Long id, String name, String status) { - Pet pet = new Pet(); - pet.setPetId(id); - pet.setPetName(name); - pet.setPetSpecies("Cat"); - pet.setPetBreed("Mixed"); - pet.setPetAge(2); - pet.setPetStatus(status); - pet.setPetPrice(java.math.BigDecimal.TEN); - return pet; - } - - private Adoption adoption(Long petId, Long userId) { - Adoption adoption = new Adoption(); - Pet pet = new Pet(); - pet.setPetId(petId); - adoption.setPet(pet); - Customer customer = new Customer(); - customer.setCustomerId(1L); - customer.setUserId(userId); - adoption.setCustomer(customer); - adoption.setAdoptionStatus("Completed"); - return adoption; - } -}