From a771a50f4021102ae0181e6eefa7ea70e78c34d0 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Sun, 1 Sep 2024 20:42:16 +0200 Subject: [PATCH 01/23] feat: create PetController to get pet list --- .../backend/controller/PetController.java | 26 +++++++++++ .../backend/dtos/PetListRequestDto.java | 25 +++++++++++ .../backend/dtos/PetListResponseDto.java | 14 ++++++ .../greenfoxacademy/backend/models/Pet.java | 2 - .../greenfoxacademy/backend/models/User.java | 2 + .../backend/repositories/PetRepository.java | 2 + .../backend/services/pet/PetService.java | 16 +++++++ .../backend/services/pet/PetServiceImpl.java | 43 +++++++++++++++++++ .../services/user/owner/OwnerServiceImpl.java | 2 +- package-lock.json | 6 +++ 10 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java create mode 100644 backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListRequestDto.java create mode 100644 backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java create mode 100644 backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java create mode 100644 backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java create mode 100644 package-lock.json diff --git a/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java b/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java new file mode 100644 index 00000000..fcde0d45 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java @@ -0,0 +1,26 @@ +package com.greenfoxacademy.backend.controller; + +import com.greenfoxacademy.backend.dtos.PetListResponseDto; +import com.greenfoxacademy.backend.services.pet.PetService; +import java.security.Principal; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * A REST controller that handles operations related to users' pets. + * + * @author Your Name + */ +@RequiredArgsConstructor +@RestController +public class PetController { + private final PetService petService; + + @GetMapping("/owner/pets") + public ResponseEntity getPets(Principal owner) { + return ResponseEntity.status(HttpStatus.OK).body(petService.getOwnerPets(owner.getName())); + } +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListRequestDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListRequestDto.java new file mode 100644 index 00000000..8f90073d --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListRequestDto.java @@ -0,0 +1,25 @@ +package com.greenfoxacademy.backend.dtos; + +import jakarta.validation.constraints.NotBlank; +import java.util.Date; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * A data transfer object for pet details. + * + * @author Your Name + */ +public record PetListRequestDto( + @NotBlank + String petName, + @NotBlank + String petBreed, + @NotBlank + String petSex, + @NotBlank + Date petBirthDate, + Date lastCheckUp, + Date nextCheckUp +) { +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java new file mode 100644 index 00000000..34ec1661 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java @@ -0,0 +1,14 @@ +package com.greenfoxacademy.backend.dtos; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * A data transfer object for a list of pets. + * + */ +public record PetListResponseDto( + List pets +){ +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java b/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java index 791ff3d6..6fd1d341 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java @@ -8,9 +8,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; - import java.util.Date; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/backend/src/main/java/com/greenfoxacademy/backend/models/User.java b/backend/src/main/java/com/greenfoxacademy/backend/models/User.java index 26c536d7..46205daf 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/models/User.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/models/User.java @@ -7,6 +7,7 @@ import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import org.springframework.security.core.userdetails.UserDetails; @@ -22,6 +23,7 @@ @NoArgsConstructor public abstract class User implements UserDetails { + @Getter @Id @GeneratedValue private Integer id; diff --git a/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java b/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java index e53bb6fe..95aaa9cd 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java @@ -1,10 +1,12 @@ package com.greenfoxacademy.backend.repositories; import com.greenfoxacademy.backend.models.Pet; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; /** * Repository to manage Pet entities. */ public interface PetRepository extends JpaRepository { + List findAllByPetOwnerId(Integer ownerId); } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java new file mode 100644 index 00000000..064d5966 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java @@ -0,0 +1,16 @@ +package com.greenfoxacademy.backend.services.pet; + +import com.greenfoxacademy.backend.dtos.PetListResponseDto; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +/** + * Retrieves the pets of the specified owner. + * + * @param name The name of the owner. + * @return A response containing the owner's pets. + */ +@Service +public interface PetService { + PetListResponseDto getOwnerPets(String name); +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java new file mode 100644 index 00000000..2b704ca8 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java @@ -0,0 +1,43 @@ +package com.greenfoxacademy.backend.services.pet; + +import com.greenfoxacademy.backend.dtos.PetListRequestDto; +import com.greenfoxacademy.backend.dtos.PetListResponseDto; +import com.greenfoxacademy.backend.models.Pet; +import com.greenfoxacademy.backend.repositories.PetRepository; +import com.greenfoxacademy.backend.services.user.owner.OwnerService; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/** + * Retrieves a list of pets owned by the user with the specified email. + */ +@Service +@RequiredArgsConstructor +public class PetServiceImpl implements PetService { + private final PetRepository petRepository; + private final OwnerService ownerService; + private final ModelMapper modelMapper = new ModelMapper(); + + /** + * Retrieves a list of pets owned by the user with the specified email. + * + * @param email the email of the pet owner + * @return a {@link PetListResponseDto} containing the list of pets + * @throws UsernameNotFoundException if the user with the specified email is not found + */ + @Override + public PetListResponseDto getOwnerPets(String email) { + List petList = petRepository + .findAllByPetOwnerId(ownerService.loadUserByUsername(email).getId()); + + List petDtoList = petList.stream() + .map(pet -> modelMapper.map(pet, PetListRequestDto.class)) + .collect(Collectors.toList()); + + return new PetListResponseDto(petDtoList); + } +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java index 69a93f83..133ef8c7 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java @@ -94,7 +94,7 @@ public ProfileUpdateResponseDto profileUpdate( } @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + public Owner loadUserByUsername(String username) throws UsernameNotFoundException { return ownerRepository.findByEmail(username) .orElseThrow(() -> new UsernameNotFoundException("No such user!")); } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..5c457c7f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Pet-Clinic-GilmoreDevs", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From d2e69be9754b408a117714a95d0c85995cf94f3e Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Mon, 2 Sep 2024 20:18:46 +0200 Subject: [PATCH 02/23] test: create PetControllerTest with mockMvc --- .../backend/controller/PetControllerTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java diff --git a/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java b/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java new file mode 100644 index 00000000..257fdf2f --- /dev/null +++ b/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java @@ -0,0 +1,84 @@ +package com.greenfoxacademy.backend.controller; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.greenfoxacademy.backend.models.Owner; +import com.greenfoxacademy.backend.models.Pet; +import com.greenfoxacademy.backend.repositories.OwnerRepository; +import com.greenfoxacademy.backend.repositories.PetRepository; +import jakarta.transaction.Transactional; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class PetControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private PetRepository petRepository; + + @Autowired + private OwnerRepository ownerRepository; + + @BeforeEach + public void setUp() { + // Set up mock data + Owner userWithPets = new Owner(); + userWithPets.setEmail("userWithPets@example.com"); + userWithPets.setPassword("Password"); + Owner userWithNoPets = new Owner(); + userWithNoPets.setEmail("userWithNoPets@example.com"); + userWithNoPets.setPassword("Password"); + + ownerRepository.saveAll(Arrays.asList(userWithPets, userWithNoPets)); + + Pet pet1 = new Pet(); + pet1.setPetName("Morzsi"); + pet1.setPetOwner(userWithPets); + Pet pet2 = new Pet(); + pet2.setPetName("Rusty"); + pet2.setPetOwner(userWithPets); + + petRepository.saveAll(Arrays.asList(pet1, pet2)); + } + + @Test + @WithMockUser(username = "userWithPets@example.com") + public void testCorrectEmailWithExistingPets() throws Exception { + mockMvc.perform(get("/owner/pets") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pets[0].petName").value("Morzsi")) + .andExpect(jsonPath("$.pets[1].petName").value("Rusty")); + } + + @Test + @WithMockUser(username = "userWithNoPets@example.com") + public void testCorrectEmailWithNoExistingPets() throws Exception { + mockMvc.perform(get("/owner/pets") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pets").isEmpty()); + } + + @Test + @WithMockUser(username = "nonExistingUser@example.com") + public void testIncorrectEmail() throws Exception { + mockMvc.perform(get("/owner/pets") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + } +} From 44728748474673a20af8cddff37c68bacbba9f55 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Thu, 5 Sep 2024 18:32:44 +0200 Subject: [PATCH 03/23] fix: fix pets arguments --- .../backend/controller/PetController.java | 2 +- ...ListRequestDto.java => PetDetailsDto.java} | 18 ++++++++-------- .../backend/dtos/PetListResponseDto.java | 4 +--- .../greenfoxacademy/backend/models/Owner.java | 2 +- .../greenfoxacademy/backend/models/Pet.java | 12 +++++------ .../backend/repositories/PetRepository.java | 2 +- .../backend/services/pet/PetServiceImpl.java | 8 +++---- .../services/user/owner/OwnerService.java | 1 + .../services/user/owner/OwnerServiceImpl.java | 21 +++++++++++++++++-- .../backend/controller/PetControllerTest.java | 20 +++++++++--------- frontend/src/httpClient.ts | 19 ++++++++++++++++- 11 files changed, 71 insertions(+), 38 deletions(-) rename backend/src/main/java/com/greenfoxacademy/backend/dtos/{PetListRequestDto.java => PetDetailsDto.java} (57%) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java b/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java index fcde0d45..b8d817a9 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java @@ -19,7 +19,7 @@ public class PetController { private final PetService petService; - @GetMapping("/owner/pets") + @GetMapping("/pets") public ResponseEntity getPets(Principal owner) { return ResponseEntity.status(HttpStatus.OK).body(petService.getOwnerPets(owner.getName())); } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListRequestDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java similarity index 57% rename from backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListRequestDto.java rename to backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java index 8f90073d..dc298108 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListRequestDto.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java @@ -2,7 +2,7 @@ import jakarta.validation.constraints.NotBlank; import java.util.Date; -import lombok.AllArgsConstructor; + import lombok.Data; /** @@ -10,16 +10,16 @@ * * @author Your Name */ -public record PetListRequestDto( +@Data +public class PetDetailsDto { @NotBlank - String petName, + String name; @NotBlank - String petBreed, + String breed; @NotBlank - String petSex, + String sex; @NotBlank - Date petBirthDate, - Date lastCheckUp, - Date nextCheckUp -) { + Date birthDate; + Date lastCheckUp; + Date nextCheckUp; } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java index 34ec1661..689b1368 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java @@ -1,14 +1,12 @@ package com.greenfoxacademy.backend.dtos; import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Data; /** * A data transfer object for a list of pets. * */ public record PetListResponseDto( - List pets + List pets ){ } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/models/Owner.java b/backend/src/main/java/com/greenfoxacademy/backend/models/Owner.java index 819e1872..18846d3b 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/models/Owner.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/models/Owner.java @@ -38,7 +38,7 @@ @AllArgsConstructor @Table(name = "_owner") public class Owner extends User { - @OneToMany(mappedBy = "petOwner") + @OneToMany(mappedBy = "owner") private List pets; @Override diff --git a/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java b/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java index 6fd1d341..bb185db0 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/models/Pet.java @@ -31,14 +31,14 @@ public class Pet { private Integer id; @Column(nullable = false) - private String petName; - private String petBreed; - private String petSex; - private Date petBirthDate; + private String name; + private String breed; + private String sex; + private Date birthDate; private Date lastCheckUp; private Date nextCheckUp; @ManyToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "petOwner_Id") - private Owner petOwner; + @JoinColumn(name = "owner_id") + private Owner owner; } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java b/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java index 95aaa9cd..d5c81cac 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java @@ -8,5 +8,5 @@ * Repository to manage Pet entities. */ public interface PetRepository extends JpaRepository { - List findAllByPetOwnerId(Integer ownerId); + List findAllByOwnerId(Integer owner); } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java index 2b704ca8..61265fd4 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java @@ -1,6 +1,6 @@ package com.greenfoxacademy.backend.services.pet; -import com.greenfoxacademy.backend.dtos.PetListRequestDto; +import com.greenfoxacademy.backend.dtos.PetDetailsDto; import com.greenfoxacademy.backend.dtos.PetListResponseDto; import com.greenfoxacademy.backend.models.Pet; import com.greenfoxacademy.backend.repositories.PetRepository; @@ -32,10 +32,10 @@ public class PetServiceImpl implements PetService { @Override public PetListResponseDto getOwnerPets(String email) { List petList = petRepository - .findAllByPetOwnerId(ownerService.loadUserByUsername(email).getId()); + .findAllByOwnerId(ownerService.findByEmail(email).getId()); - List petDtoList = petList.stream() - .map(pet -> modelMapper.map(pet, PetListRequestDto.class)) + List petDtoList = petList.stream() + .map(pet -> modelMapper.map(pet, PetDetailsDto.class)) .collect(Collectors.toList()); return new PetListResponseDto(petDtoList); diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java index 68dba21e..9e9d0358 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java @@ -20,6 +20,7 @@ */ @Service public interface OwnerService extends UserDetailsService { + Owner findByEmail(String username); RegisterResponseDto register(RegisterRequestDto userDto) throws UserAlreadyExistsError; LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception; diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java index 133ef8c7..26413935 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java @@ -93,8 +93,7 @@ public ProfileUpdateResponseDto profileUpdate( return new ProfileUpdateResponseDto(authService.generateToken(updatedUser)); } - @Override - public Owner loadUserByUsername(String username) throws UsernameNotFoundException { + public Owner findByEmail(String username) throws UsernameNotFoundException { return ownerRepository.findByEmail(username) .orElseThrow(() -> new UsernameNotFoundException("No such user!")); } @@ -116,4 +115,22 @@ public void verifyUser(UUID id) { userWithId.setVerificationId(null); ownerRepository.save(userWithId); } + + /** + * Locates the user based on the username. In the actual implementation, the search + * may possibly be case sensitive, or case insensitive depending on how the + * implementation instance is configured. In this case, the UserDetails + * object that comes back may have a username that is of a different case than what + * was actually requested.. + * + * @param username the username identifying the user whose data is required. + * @return a fully populated user record (never null) + * @throws UsernameNotFoundException if the user could not be found or the user has no + * GrantedAuthority + */ + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return ownerRepository.findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("No such user!")); + } } \ No newline at end of file diff --git a/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java b/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java index 257fdf2f..a7e3fc04 100644 --- a/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java +++ b/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java @@ -46,11 +46,11 @@ public void setUp() { ownerRepository.saveAll(Arrays.asList(userWithPets, userWithNoPets)); Pet pet1 = new Pet(); - pet1.setPetName("Morzsi"); - pet1.setPetOwner(userWithPets); + pet1.setName("Morzsi"); + pet1.setOwner(userWithPets); Pet pet2 = new Pet(); - pet2.setPetName("Rusty"); - pet2.setPetOwner(userWithPets); + pet2.setName("Rusty"); + pet2.setOwner(userWithPets); petRepository.saveAll(Arrays.asList(pet1, pet2)); } @@ -58,17 +58,17 @@ public void setUp() { @Test @WithMockUser(username = "userWithPets@example.com") public void testCorrectEmailWithExistingPets() throws Exception { - mockMvc.perform(get("/owner/pets") + mockMvc.perform(get("/pets") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.pets[0].petName").value("Morzsi")) - .andExpect(jsonPath("$.pets[1].petName").value("Rusty")); + .andExpect(jsonPath("$.pets[0].name").value("Morzsi")) + .andExpect(jsonPath("$.pets[1].name").value("Rusty")); } @Test @WithMockUser(username = "userWithNoPets@example.com") public void testCorrectEmailWithNoExistingPets() throws Exception { - mockMvc.perform(get("/owner/pets") + mockMvc.perform(get("/pets") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.pets").isEmpty()); @@ -77,8 +77,8 @@ public void testCorrectEmailWithNoExistingPets() throws Exception { @Test @WithMockUser(username = "nonExistingUser@example.com") public void testIncorrectEmail() throws Exception { - mockMvc.perform(get("/owner/pets") + mockMvc.perform(get("/pets") .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); + .andExpect(status().is4xxClientError()); } } diff --git a/frontend/src/httpClient.ts b/frontend/src/httpClient.ts index 2f25ee76..d902d6de 100644 --- a/frontend/src/httpClient.ts +++ b/frontend/src/httpClient.ts @@ -10,6 +10,23 @@ const httpClient = axios.create({ }, }); +export type PetDetails = { + name: string; + breed: string; + sex: string; + birthDate: Date; + lastCheckUp: Date; + nextCheckUp: Date; +}; + +type PetListResponse = { + pets: PetDetails[]; +}; + +const petList = () => { + return httpClient.get("/pets"); +}; + type RegisterRequest = { email: string; password: string; @@ -70,4 +87,4 @@ const logout = () => { httpClient.defaults.headers.common.Authorization = undefined; }; -export { login, register, logout, updateProfile, deleteProfile }; +export { login, register, logout, updateProfile, deleteProfile, petList }; From 045d984bc59b4b62b6c8406c5f25ffed83f3476d Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Sat, 7 Sep 2024 09:28:54 +0200 Subject: [PATCH 04/23] feature: PetList has been created and made some reviewdog repairings --- frontend/src/pages/PetList.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 frontend/src/pages/PetList.tsx diff --git a/frontend/src/pages/PetList.tsx b/frontend/src/pages/PetList.tsx new file mode 100644 index 00000000..e1f0d9d9 --- /dev/null +++ b/frontend/src/pages/PetList.tsx @@ -0,0 +1,30 @@ +import {PetDetails, petList} from "../httpClient.ts"; +import {useEffect, useState} from "react"; + +export const PetList = () => { + const [pets, setPets] = useState(); + + useEffect(() => { + petList().then( + petsResponse => setPets(petsResponse.data.pets) + ); + }, []); + + + return<> + + + {pets?.map((pet) => + + + + + )} +
+

Name

+

Breed

+

Sex

+

BirthDate

+
{pet.name}{pet.breed}{pet.sex}{pet.birthDate.toDateString()}
+ +} From 50033a972422cdde775a7465418aa3fdf9ffef92 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Sat, 7 Sep 2024 10:28:00 +0200 Subject: [PATCH 05/23] chore: some reviewdog repairings --- .../backend/dtos/PetDetailsDto.java | 21 +++++++++---------- .../backend/services/pet/PetService.java | 2 +- .../services/user/owner/OwnerService.java | 1 + .../services/mail/EmailServiceImplTest.java | 5 +++++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java index dc298108..b9090d0d 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.NotBlank; import java.util.Date; - import lombok.Data; /** @@ -12,14 +11,14 @@ */ @Data public class PetDetailsDto { - @NotBlank - String name; - @NotBlank - String breed; - @NotBlank - String sex; - @NotBlank - Date birthDate; - Date lastCheckUp; - Date nextCheckUp; + @NotBlank + String name; + @NotBlank + String breed; + @NotBlank + String sex; + @NotBlank + Date birthDate; + Date lastCheckUp; + Date nextCheckUp; } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java index 064d5966..39d4c761 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java @@ -10,7 +10,7 @@ * @param name The name of the owner. * @return A response containing the owner's pets. */ -@Service + public interface PetService { PetListResponseDto getOwnerPets(String name); } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java index 9e9d0358..99a02f70 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java @@ -21,6 +21,7 @@ @Service public interface OwnerService extends UserDetailsService { Owner findByEmail(String username); + RegisterResponseDto register(RegisterRequestDto userDto) throws UserAlreadyExistsError; LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception; diff --git a/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java b/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java index 616bdc12..574c4d6a 100644 --- a/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java +++ b/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java @@ -5,6 +5,7 @@ import static org.mockito.Mockito.when; import com.greenfoxacademy.backend.config.EmailConfiguration; +import com.greenfoxacademy.backend.services.pet.PetService; import com.greenfoxacademy.backend.services.user.owner.OwnerService; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; @@ -36,6 +37,10 @@ class EmailServiceImplTest { @MockBean private OwnerService ownerService; + @MockBean + private PetService petService; + + private EmailServiceImpl emailService; @BeforeEach From c73d38f1384dce2c9f4a617f391cb05195240105 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Sat, 7 Sep 2024 10:58:17 +0200 Subject: [PATCH 06/23] feature: create petList button under main page --- frontend/src/pages/Main.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/Main.tsx b/frontend/src/pages/Main.tsx index 6ab72834..bc08d77f 100644 --- a/frontend/src/pages/Main.tsx +++ b/frontend/src/pages/Main.tsx @@ -20,9 +20,14 @@ export function Main() { )} {isAuthenticated && ( - - Profile - + <> + + Profile + + + PetList + + )} ); From 791af79ee5ab2a86e4bf26e3f75383771e4f1671 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Sun, 8 Sep 2024 18:54:39 +0200 Subject: [PATCH 07/23] feature: add catch error for the PetList --- frontend/src/pages/PetList.tsx | 50 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/frontend/src/pages/PetList.tsx b/frontend/src/pages/PetList.tsx index e1f0d9d9..1c213c01 100644 --- a/frontend/src/pages/PetList.tsx +++ b/frontend/src/pages/PetList.tsx @@ -1,30 +1,42 @@ -import {PetDetails, petList} from "../httpClient.ts"; -import {useEffect, useState} from "react"; +import { PetDetails, petList } from "../httpClient.ts"; +import { useEffect, useState } from "react"; -export const PetList = () => { - const [pets, setPets] = useState(); +const PetList = () => { + const [pets, setPets] = useState([]); useEffect(() => { petList().then( petsResponse => setPets(petsResponse.data.pets) - ); + ).catch(error => { + console.error("Error fetching pets:", error); + }); }, []); - - return<> + return ( + <> +

Please choose from your registered Pets!

- - {pets?.map((pet) => - - - - - )} + + + + + + + + + + {pets?.map((pet) => ( + + + + + + + ))} +
-

Name

-

Breed

-

Sex

-

BirthDate

-
{pet.name}{pet.breed}{pet.sex}{pet.birthDate.toDateString()}
NameBreedSexBirthDate
{pet.name}{pet.breed}{pet.sex}{new Date(pet.birthDate).toDateString()}
+ ); } + +export default PetList; \ No newline at end of file From da21c15b1734d06c13782d615380abc8ae44cfe8 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Mon, 9 Sep 2024 15:06:43 +0200 Subject: [PATCH 08/23] feature: add petlist to the router in App.tsx --- frontend/src/App.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0eb9c2df..4b54439b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import { ProfileDeletion } from "./pages/ProfileDeletion.tsx"; import { ProfileDetail } from "./pages/ProfileDetail.tsx"; import { ProfileUpdate } from "./pages/ProfileUpdate.tsx"; import { Register } from "./pages/Register"; +import PetList from "./pages/PetList.tsx"; const router = createBrowserRouter([ { @@ -33,6 +34,10 @@ const router = createBrowserRouter([ path: "delete-profile", element: , }, + { + path: "pets", + element: , + }, ]); function App() { From 22800f88d4b7f4f70d2e8a02e8426b2165a573d2 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Mon, 9 Sep 2024 15:07:26 +0200 Subject: [PATCH 09/23] chore: format the table of pets --- frontend/src/pages/PetList.tsx | 72 +++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/frontend/src/pages/PetList.tsx b/frontend/src/pages/PetList.tsx index 1c213c01..14f9bf5e 100644 --- a/frontend/src/pages/PetList.tsx +++ b/frontend/src/pages/PetList.tsx @@ -15,26 +15,60 @@ const PetList = () => { return ( <>

Please choose from your registered Pets!

- - - - - - - - - - - {pets?.map((pet) => ( - - - - - + {pets.length > 0 ? ( +
NameBreedSexBirthDate
{pet.name}{pet.breed}{pet.sex}{new Date(pet.birthDate).toDateString()}
+ + + + + + - ))} - -
Name + Breed + Sex + BirthDate +
+ + + {pets.map((pet, index) => ( + + {pet.name} + {pet.breed} + {pet.sex} + {new Date(pet.birthDate).toDateString()} + + ))} + + + ) : ( +

No pets registered.

+ )} ); } From 5be7816a8bbd8d166b49660a4db4cd585dc714bd Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Mon, 9 Sep 2024 15:27:58 +0200 Subject: [PATCH 10/23] test: test PetList --- frontend/src/httpClient.test.ts | 8 ++++- frontend/src/pages/PetList.test.tsx | 54 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/PetList.test.tsx diff --git a/frontend/src/httpClient.test.ts b/frontend/src/httpClient.test.ts index fce76fa1..55604bd0 100644 --- a/frontend/src/httpClient.test.ts +++ b/frontend/src/httpClient.test.ts @@ -5,9 +5,10 @@ vi.mock("./httpClient", () => ({ logout: vi.fn(), register: vi.fn(), updateProfile: vi.fn(), + petList: vi.fn(), })); -import { login, logout, register, updateProfile } from "./httpClient"; +import { login, logout, register, updateProfile, petList } from "./httpClient"; describe("httpClient", () => { it("should call login", async () => { @@ -45,4 +46,9 @@ describe("httpClient", () => { expect(login).toHaveBeenCalledTimes(2); expect(login).toHaveBeenCalledWith({ email: "test", password: "test" }); }); + + it("should call petList", () => { + petList(); + expect(petList).toHaveBeenCalledTimes(1); + }); }); diff --git a/frontend/src/pages/PetList.test.tsx b/frontend/src/pages/PetList.test.tsx new file mode 100644 index 00000000..bcaa6be8 --- /dev/null +++ b/frontend/src/pages/PetList.test.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import PetList from "./PetList"; +import { petList } from "../httpClient"; + +// Mock the petList function +vi.mock("../httpClient", () => ({ + petList: vi.fn(), +})); + +const mockPets = [ + { + name: "Buddy", + breed: "Golden Retriever", + sex: "Male", + birthDate: new Date("2018-01-01"), + lastCheckUp: new Date("2023-01-01"), + nextCheckUp: new Date("2024-01-01"), + }, + { + name: "Mittens", + breed: "Tabby", + sex: "Female", + birthDate: new Date("2019-05-15"), + lastCheckUp: new Date("2023-05-15"), + nextCheckUp: new Date("2024-05-15"), + }, +]; + +describe("PetList Component", () => { + it("displays pets in a table when data is available", async () => { + (petList as vi.mock).mockResolvedValueOnce({ data: { pets: mockPets } }); + + render(); + + await waitFor(() => { + expect(screen.getByText("Buddy")).toBeInTheDocument(); + expect(screen.getByText("Golden Retriever")).toBeInTheDocument(); + expect(screen.getByText("Mittens")).toBeInTheDocument(); + expect(screen.getByText("Tabby")).toBeInTheDocument(); + }); + }); + + it("displays a message when no pets are registered", async () => { + (petList as vi.mock).mockResolvedValueOnce({ data: { pets: [] } }); + + render(); + + await waitFor(() => { + expect(screen.getByText("No pets registered.")).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file From 88633e77029d7096e551f260e63f3d7ebf46fc55 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Mon, 9 Sep 2024 17:26:31 +0200 Subject: [PATCH 11/23] test: add PetServiceImpl test --- .../Pet/PetServiceImplTest.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename backend/src/test/java/com/greenfoxacademy/backend/{controller/PetControllerTest.java => services/Pet/PetServiceImplTest.java} (97%) diff --git a/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java b/backend/src/test/java/com/greenfoxacademy/backend/services/Pet/PetServiceImplTest.java similarity index 97% rename from backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java rename to backend/src/test/java/com/greenfoxacademy/backend/services/Pet/PetServiceImplTest.java index a7e3fc04..0ffa5c45 100644 --- a/backend/src/test/java/com/greenfoxacademy/backend/controller/PetControllerTest.java +++ b/backend/src/test/java/com/greenfoxacademy/backend/services/Pet/PetServiceImplTest.java @@ -1,4 +1,4 @@ -package com.greenfoxacademy.backend.controller; +package com.greenfoxacademy.backend.services.Pet; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -22,7 +22,7 @@ @SpringBootTest @AutoConfigureMockMvc @Transactional -public class PetControllerTest { +public class PetServiceImplTest { @Autowired private MockMvc mockMvc; From d4383a4f3f0f54fa58cd7f789204f51623cf9031 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Mon, 9 Sep 2024 18:10:51 +0200 Subject: [PATCH 12/23] test: add test points to ProfileUpdate --- frontend/src/pages/ProfileUpdate.test.tsx | 55 ++++++++++++++++++----- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/ProfileUpdate.test.tsx b/frontend/src/pages/ProfileUpdate.test.tsx index 172f9f27..5863d90c 100644 --- a/frontend/src/pages/ProfileUpdate.test.tsx +++ b/frontend/src/pages/ProfileUpdate.test.tsx @@ -1,20 +1,53 @@ -import { render } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { ProfileUpdate } from "./ProfileUpdate.tsx"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { useProfileUpdateState } from "../hooks/useProfileUpdate"; + +vi.mock("../hooks/useProfileUpdate"); + +const mockUseProfileUpdateState = useProfileUpdateState as vi.MockedFunction; describe("ProfileUpdate", () => { - it("should render successfully", () => { - const component = render(, { - wrapper: BrowserRouter, + beforeEach(() => { + mockUseProfileUpdateState.mockReturnValue({ + state: {user: {email: "", firstName: "", lastName: "", password: ""}}, + updateUserField: vi.fn(), + updateUserProfile: vi.fn(), + navigate: vi.fn(), }); - expect(component).not.toBeNull(); }); - it("should call ", () => { - const component = render(, { - wrapper: BrowserRouter, + it("renders the form fields correctly", () => { + render(); + + expect(screen.getByLabelText(/Email:/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/FirstName:/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/LastName:/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Password:/i)).toBeInTheDocument(); + }); + + it("calls updateUserField on input change", () => { + const {updateUserField} = mockUseProfileUpdateState(); + render(); + + fireEvent.change(screen.getByLabelText(/Email:/i), {target: {value: "test@example.com"}}); + expect(updateUserField).toHaveBeenCalledWith("email", "test@example.com"); + }); + + describe("ProfileUpdate", () => { + it("should render successfully", () => { + const component = render(, { + wrapper: BrowserRouter, + }); + expect(component).not.toBeNull(); + }); + + it("should call ", () => { + const component = render(, { + wrapper: BrowserRouter, + }); + expect(component).not.toBeNull(); }); - expect(component).not.toBeNull(); }); -}); +}) From f0b3bf891b2b8e9a62db07d55533cf8244a8fc98 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Mon, 9 Sep 2024 18:25:26 +0200 Subject: [PATCH 13/23] fix: linter fix --- frontend/src/App.tsx | 8 +- frontend/src/httpClient.test.ts | 2 +- frontend/src/pages/PetList.test.tsx | 69 +++++---- frontend/src/pages/PetList.tsx | 169 +++++++++++++--------- frontend/src/pages/ProfileUpdate.test.tsx | 26 ++-- 5 files changed, 153 insertions(+), 121 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4b54439b..a8648217 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,13 @@ import "./App.css"; import { RouterProvider, createBrowserRouter } from "react-router-dom"; -import { Login } from "./pages/Login"; -import { Main } from "./pages/Main"; +import { Login } from "./pages/Login.tsx"; +import { Main } from "./pages/Main.tsx"; +import PetList from "./pages/PetList.tsx"; import { ProfileDeletion } from "./pages/ProfileDeletion.tsx"; import { ProfileDetail } from "./pages/ProfileDetail.tsx"; import { ProfileUpdate } from "./pages/ProfileUpdate.tsx"; -import { Register } from "./pages/Register"; -import PetList from "./pages/PetList.tsx"; +import { Register } from "./pages/Register.tsx"; const router = createBrowserRouter([ { diff --git a/frontend/src/httpClient.test.ts b/frontend/src/httpClient.test.ts index 55604bd0..dafcdf2a 100644 --- a/frontend/src/httpClient.test.ts +++ b/frontend/src/httpClient.test.ts @@ -8,7 +8,7 @@ vi.mock("./httpClient", () => ({ petList: vi.fn(), })); -import { login, logout, register, updateProfile, petList } from "./httpClient"; +import { login, logout, petList, register, updateProfile } from "./httpClient"; describe("httpClient", () => { it("should call login", async () => { diff --git a/frontend/src/pages/PetList.test.tsx b/frontend/src/pages/PetList.test.tsx index bcaa6be8..d1a4dd63 100644 --- a/frontend/src/pages/PetList.test.tsx +++ b/frontend/src/pages/PetList.test.tsx @@ -1,54 +1,53 @@ -import React from "react"; import { render, screen, waitFor } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; -import PetList from "./PetList"; import { petList } from "../httpClient"; +import PetList from "./PetList"; // Mock the petList function vi.mock("../httpClient", () => ({ - petList: vi.fn(), + petList: vi.fn(), })); const mockPets = [ - { - name: "Buddy", - breed: "Golden Retriever", - sex: "Male", - birthDate: new Date("2018-01-01"), - lastCheckUp: new Date("2023-01-01"), - nextCheckUp: new Date("2024-01-01"), - }, - { - name: "Mittens", - breed: "Tabby", - sex: "Female", - birthDate: new Date("2019-05-15"), - lastCheckUp: new Date("2023-05-15"), - nextCheckUp: new Date("2024-05-15"), - }, + { + name: "Buddy", + breed: "Golden Retriever", + sex: "Male", + birthDate: new Date("2018-01-01"), + lastCheckUp: new Date("2023-01-01"), + nextCheckUp: new Date("2024-01-01"), + }, + { + name: "Mittens", + breed: "Tabby", + sex: "Female", + birthDate: new Date("2019-05-15"), + lastCheckUp: new Date("2023-05-15"), + nextCheckUp: new Date("2024-05-15"), + }, ]; describe("PetList Component", () => { - it("displays pets in a table when data is available", async () => { - (petList as vi.mock).mockResolvedValueOnce({ data: { pets: mockPets } }); + it("displays pets in a table when data is available", async () => { + (petList as vi.mock).mockResolvedValueOnce({ data: { pets: mockPets } }); - render(); + render(); - await waitFor(() => { - expect(screen.getByText("Buddy")).toBeInTheDocument(); - expect(screen.getByText("Golden Retriever")).toBeInTheDocument(); - expect(screen.getByText("Mittens")).toBeInTheDocument(); - expect(screen.getByText("Tabby")).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText("Buddy")).toBeInTheDocument(); + expect(screen.getByText("Golden Retriever")).toBeInTheDocument(); + expect(screen.getByText("Mittens")).toBeInTheDocument(); + expect(screen.getByText("Tabby")).toBeInTheDocument(); }); + }); - it("displays a message when no pets are registered", async () => { - (petList as vi.mock).mockResolvedValueOnce({ data: { pets: [] } }); + it("displays a message when no pets are registered", async () => { + (petList as vi.mock).mockResolvedValueOnce({ data: { pets: [] } }); - render(); + render(); - await waitFor(() => { - expect(screen.getByText("No pets registered.")).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText("No pets registered.")).toBeInTheDocument(); }); -}); \ No newline at end of file + }); +}); diff --git a/frontend/src/pages/PetList.tsx b/frontend/src/pages/PetList.tsx index 14f9bf5e..8b5a6994 100644 --- a/frontend/src/pages/PetList.tsx +++ b/frontend/src/pages/PetList.tsx @@ -1,76 +1,105 @@ -import { PetDetails, petList } from "../httpClient.ts"; import { useEffect, useState } from "react"; +import { type PetDetails, petList } from "../httpClient.ts"; const PetList = () => { - const [pets, setPets] = useState([]); + const [pets, setPets] = useState([]); - useEffect(() => { - petList().then( - petsResponse => setPets(petsResponse.data.pets) - ).catch(error => { - console.error("Error fetching pets:", error); - }); - }, []); + useEffect(() => { + petList() + .then((petsResponse) => setPets(petsResponse.data.pets)) + .catch((error) => { + console.error("Error fetching pets:", error); + }); + }, []); - return ( - <> -

Please choose from your registered Pets!

- {pets.length > 0 ? ( - - - - - - - - - - - {pets.map((pet, index) => ( - - - - - - - ))} - -
Name - Breed - Sex - BirthDate -
{pet.name}{pet.breed}{pet.sex}{new Date(pet.birthDate).toDateString()}
- ) : ( -

No pets registered.

- )} - - ); -} + return ( + <> +

Please choose from your registered Pets!

+ {pets.length > 0 ? ( + + + + + + + + + + + {pets.map((pet, index) => ( + + + + + + + ))} + +
+ Name + + Breed + + Sex + + BirthDate +
+ {pet.name} + + {pet.breed} + + {pet.sex} + + {new Date(pet.birthDate).toDateString()} +
+ ) : ( +

No pets registered.

+ )} + + ); +}; -export default PetList; \ No newline at end of file +export default PetList; diff --git a/frontend/src/pages/ProfileUpdate.test.tsx b/frontend/src/pages/ProfileUpdate.test.tsx index 5863d90c..1839f9b2 100644 --- a/frontend/src/pages/ProfileUpdate.test.tsx +++ b/frontend/src/pages/ProfileUpdate.test.tsx @@ -1,17 +1,19 @@ +import { fireEvent, render, screen } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; import { describe, expect, it, vi } from "vitest"; -import { ProfileUpdate } from "./ProfileUpdate.tsx"; -import { render, screen, fireEvent } from "@testing-library/react"; import { useProfileUpdateState } from "../hooks/useProfileUpdate"; +import { ProfileUpdate } from "./ProfileUpdate.tsx"; vi.mock("../hooks/useProfileUpdate"); -const mockUseProfileUpdateState = useProfileUpdateState as vi.MockedFunction; +const mockUseProfileUpdateState = useProfileUpdateState as vi.MockedFunction< + typeof useProfileUpdateState +>; describe("ProfileUpdate", () => { beforeEach(() => { mockUseProfileUpdateState.mockReturnValue({ - state: {user: {email: "", firstName: "", lastName: "", password: ""}}, + state: { user: { email: "", firstName: "", lastName: "", password: "" } }, updateUserField: vi.fn(), updateUserProfile: vi.fn(), navigate: vi.fn(), @@ -19,7 +21,7 @@ describe("ProfileUpdate", () => { }); it("renders the form fields correctly", () => { - render(); + render(); expect(screen.getByLabelText(/Email:/i)).toBeInTheDocument(); expect(screen.getByLabelText(/FirstName:/i)).toBeInTheDocument(); @@ -28,26 +30,28 @@ describe("ProfileUpdate", () => { }); it("calls updateUserField on input change", () => { - const {updateUserField} = mockUseProfileUpdateState(); - render(); + const { updateUserField } = mockUseProfileUpdateState(); + render(); - fireEvent.change(screen.getByLabelText(/Email:/i), {target: {value: "test@example.com"}}); + fireEvent.change(screen.getByLabelText(/Email:/i), { + target: { value: "test@example.com" }, + }); expect(updateUserField).toHaveBeenCalledWith("email", "test@example.com"); }); describe("ProfileUpdate", () => { it("should render successfully", () => { - const component = render(, { + const component = render(, { wrapper: BrowserRouter, }); expect(component).not.toBeNull(); }); it("should call ", () => { - const component = render(, { + const component = render(, { wrapper: BrowserRouter, }); expect(component).not.toBeNull(); }); }); -}) +}); From e94eb2abb5093799e9ef7153c5ce3d54518d6175 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Tue, 10 Sep 2024 17:35:41 +0200 Subject: [PATCH 14/23] chore: add "/pets" endpoint to the securityFilterChain --- .../java/com/greenfoxacademy/backend/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/config/SecurityConfig.java b/backend/src/main/java/com/greenfoxacademy/backend/config/SecurityConfig.java index d0d49302..98aede3d 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/config/SecurityConfig.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/config/SecurityConfig.java @@ -58,6 +58,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .authorizeHttpRequests((authorize) -> authorize .requestMatchers(allowedUrls).permitAll() .requestMatchers("/profile-update").authenticated() + .requestMatchers("/pets").authenticated() .anyRequest().authenticated() ) .cors(cors -> cors.configurationSource(corsConfigurationSource())) From 159408d170f7265f4415762a8aa0c1aa9d154348 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Tue, 10 Sep 2024 17:57:18 +0200 Subject: [PATCH 15/23] chore: validate birthDate, lastCheckUp and nextCheckUp of Pet class --- .../com/greenfoxacademy/backend/dtos/PetDetailsDto.java | 7 ++++++- .../backend/repositories/PetRepository.java | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java index b9090d0d..1ac0aa48 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java @@ -1,7 +1,10 @@ package com.greenfoxacademy.backend.dtos; +import jakarta.validation.constraints.FutureOrPresent; import jakarta.validation.constraints.NotBlank; import java.util.Date; + +import jakarta.validation.constraints.PastOrPresent; import lombok.Data; /** @@ -17,8 +20,10 @@ public class PetDetailsDto { String breed; @NotBlank String sex; - @NotBlank + @PastOrPresent(message = "The birth date must be in the past or present") Date birthDate; + @PastOrPresent(message = "The last check-up date must be in the past or present") Date lastCheckUp; + @FutureOrPresent(message = "The next check-up date must be in the future or present") Date nextCheckUp; } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java b/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java index d5c81cac..c355382c 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/repositories/PetRepository.java @@ -8,5 +8,5 @@ * Repository to manage Pet entities. */ public interface PetRepository extends JpaRepository { - List findAllByOwnerId(Integer owner); + List findAllByOwnerId(Integer ownerId); } From fe91effc83b6941aac6ac888161a27cf1c36f17e Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Tue, 10 Sep 2024 18:12:23 +0200 Subject: [PATCH 16/23] chore: delete not used import in OwnerServiceImpl --- .../backend/services/user/owner/OwnerServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java index 26413935..ee8599e8 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java @@ -12,7 +12,6 @@ import com.greenfoxacademy.backend.repositories.OwnerRepository; import com.greenfoxacademy.backend.services.auth.AuthService; import com.greenfoxacademy.backend.services.mail.EmailService; -import com.greenfoxacademy.backend.services.user.owner.OwnerService; import jakarta.transaction.Transactional; import java.util.UUID; import lombok.RequiredArgsConstructor; From 485c965c97c8da4740da06ef943989b5d5142c7c Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Tue, 10 Sep 2024 18:19:56 +0200 Subject: [PATCH 17/23] chore: add loadUserByUserName func to OwnerService.java --- .../backend/services/user/owner/OwnerService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java index 99a02f70..bc70f27a 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java @@ -12,7 +12,10 @@ import com.greenfoxacademy.backend.errors.UserAlreadyExistsError; import com.greenfoxacademy.backend.models.Owner; import java.util.UUID; + +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; /** @@ -20,6 +23,8 @@ */ @Service public interface OwnerService extends UserDetailsService { + UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; + Owner findByEmail(String username); RegisterResponseDto register(RegisterRequestDto userDto) throws UserAlreadyExistsError; From 57b37e686820d4a4af4b23898d7c1a9d8d3d7d8d Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Tue, 10 Sep 2024 18:29:51 +0200 Subject: [PATCH 18/23] fix: repair reviewdog finding --- .../java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java | 3 +-- .../backend/services/user/owner/OwnerService.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java index 1ac0aa48..a7b5be97 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java @@ -2,9 +2,8 @@ import jakarta.validation.constraints.FutureOrPresent; import jakarta.validation.constraints.NotBlank; -import java.util.Date; - import jakarta.validation.constraints.PastOrPresent; +import java.util.Date; import lombok.Data; /** diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java index bc70f27a..d9ba8d0b 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerService.java @@ -12,7 +12,6 @@ import com.greenfoxacademy.backend.errors.UserAlreadyExistsError; import com.greenfoxacademy.backend.models.Owner; import java.util.UUID; - import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; From d8287e8d55b08c4e40259a889e294b59f57a369c Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Tue, 10 Sep 2024 18:48:41 +0200 Subject: [PATCH 19/23] fix: repair reviewdog finding --- .../services/mail/EmailServiceImpl.java | 1 - .../services/user/owner/OwnerServiceImpl.java | 2 -- .../{Pet => pet}/PetServiceImplTest.java | 30 ++++++++++++++----- 3 files changed, 22 insertions(+), 11 deletions(-) rename backend/src/test/java/com/greenfoxacademy/backend/services/{Pet => pet}/PetServiceImplTest.java (71%) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/mail/EmailServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/mail/EmailServiceImpl.java index b2044b9e..48b59fc7 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/mail/EmailServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/mail/EmailServiceImpl.java @@ -6,7 +6,6 @@ import jakarta.mail.internet.MimeMessage; import java.util.UUID; import lombok.RequiredArgsConstructor; - import org.springframework.core.io.ClassPathResource; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java index 293efde7..7c6fd4d0 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/owner/OwnerServiceImpl.java @@ -14,9 +14,7 @@ import com.greenfoxacademy.backend.services.auth.AuthService; import com.greenfoxacademy.backend.services.mail.EmailService; import jakarta.transaction.Transactional; - import java.util.UUID; - import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; diff --git a/backend/src/test/java/com/greenfoxacademy/backend/services/Pet/PetServiceImplTest.java b/backend/src/test/java/com/greenfoxacademy/backend/services/pet/PetServiceImplTest.java similarity index 71% rename from backend/src/test/java/com/greenfoxacademy/backend/services/Pet/PetServiceImplTest.java rename to backend/src/test/java/com/greenfoxacademy/backend/services/pet/PetServiceImplTest.java index 0ffa5c45..c6531187 100644 --- a/backend/src/test/java/com/greenfoxacademy/backend/services/Pet/PetServiceImplTest.java +++ b/backend/src/test/java/com/greenfoxacademy/backend/services/pet/PetServiceImplTest.java @@ -1,7 +1,7 @@ -package com.greenfoxacademy.backend.services.Pet; +package com.greenfoxacademy.backend.services.pet; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.greenfoxacademy.backend.models.Owner; @@ -19,6 +19,15 @@ import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; +/** + * Integration test class for PetServiceImpl. + * This class uses the following annotations: + * - {@link SpringBootTest}: Indicates that the class is a Spring Boot test that + * will start the full application context. + * - {@link AutoConfigureMockMvc}: Automatically configures MockMvc for testing web layer. + * - {@link Transactional}: Ensures that each test method runs within a transaction + * that is rolled back after the test completes. + */ @SpringBootTest @AutoConfigureMockMvc @Transactional @@ -33,6 +42,11 @@ public class PetServiceImplTest { @Autowired private OwnerRepository ownerRepository; + /** + * Sets up mock data before each test. + * This method is executed before each test method in the current test class. + * It initializes mock data for owners and pets and saves them to the repository. + */ @BeforeEach public void setUp() { // Set up mock data @@ -60,9 +74,9 @@ public void setUp() { public void testCorrectEmailWithExistingPets() throws Exception { mockMvc.perform(get("/pets") .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.pets[0].name").value("Morzsi")) - .andExpect(jsonPath("$.pets[1].name").value("Rusty")); + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pets[0].name").value("Morzsi")) + .andExpect(jsonPath("$.pets[1].name").value("Rusty")); } @Test @@ -70,8 +84,8 @@ public void testCorrectEmailWithExistingPets() throws Exception { public void testCorrectEmailWithNoExistingPets() throws Exception { mockMvc.perform(get("/pets") .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.pets").isEmpty()); + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pets").isEmpty()); } @Test @@ -79,6 +93,6 @@ public void testCorrectEmailWithNoExistingPets() throws Exception { public void testIncorrectEmail() throws Exception { mockMvc.perform(get("/pets") .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().is4xxClientError()); + .andExpect(status().is4xxClientError()); } } From b5ad13a2237c5afcc78d09a4d857a16c68b4c236 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Wed, 18 Sep 2024 20:28:41 +0200 Subject: [PATCH 20/23] chore: delete .tsx extension from the import lines in App.tsx --- frontend/src/App.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a8648217..63537a64 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,13 @@ import "./App.css"; import { RouterProvider, createBrowserRouter } from "react-router-dom"; -import { Login } from "./pages/Login.tsx"; -import { Main } from "./pages/Main.tsx"; -import PetList from "./pages/PetList.tsx"; -import { ProfileDeletion } from "./pages/ProfileDeletion.tsx"; -import { ProfileDetail } from "./pages/ProfileDetail.tsx"; -import { ProfileUpdate } from "./pages/ProfileUpdate.tsx"; -import { Register } from "./pages/Register.tsx"; +import { Login } from "./pages/Login"; +import { Main } from "./pages/Main"; +import PetList from "./pages/PetList"; +import { ProfileDeletion } from "./pages/ProfileDeletion"; +import { ProfileDetail } from "./pages/ProfileDetail"; +import { ProfileUpdate } from "./pages/ProfileUpdate"; +import { Register } from "./pages/Register"; const router = createBrowserRouter([ { From 2af64400272d5bd91c49756c5044872160ecc3a8 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Wed, 18 Sep 2024 20:35:45 +0200 Subject: [PATCH 21/23] chore: delete lastCheckUp and nextCheckUp date from httpClient.ts PetDetails type because they are not needed here --- frontend/src/httpClient.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/httpClient.ts b/frontend/src/httpClient.ts index d902d6de..5a8cc3c8 100644 --- a/frontend/src/httpClient.ts +++ b/frontend/src/httpClient.ts @@ -15,8 +15,6 @@ export type PetDetails = { breed: string; sex: string; birthDate: Date; - lastCheckUp: Date; - nextCheckUp: Date; }; type PetListResponse = { From 42a8926ac03e7493895fd70d41992a64cf6ceb36 Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Wed, 18 Sep 2024 20:53:02 +0200 Subject: [PATCH 22/23] chore: wrap PetList into ProtectedPages in App.tsx --- frontend/src/App.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 63537a64..afafb005 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,6 +8,7 @@ import { ProfileDeletion } from "./pages/ProfileDeletion"; import { ProfileDetail } from "./pages/ProfileDetail"; import { ProfileUpdate } from "./pages/ProfileUpdate"; import { Register } from "./pages/Register"; +import { ProtectedPage } from "./pages/utils/ProtectedPage"; const router = createBrowserRouter([ { @@ -36,7 +37,9 @@ const router = createBrowserRouter([ }, { path: "pets", - element: , + element: + + , }, ]); From d3c724728bf6605af017ae3790f83e7155d7d5fe Mon Sep 17 00:00:00 2001 From: Hsbalazs Date: Wed, 18 Sep 2024 20:56:06 +0200 Subject: [PATCH 23/23] chore: linter repairing --- frontend/src/App.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index afafb005..90a32da7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -37,9 +37,11 @@ const router = createBrowserRouter([ }, { path: "pets", - element: - - , + element: ( + + + + ), }, ]);