Skip to content

Commit

Permalink
Merge pull request #107 from gfa-cc-after/SCRUM-95
Browse files Browse the repository at this point in the history
Scrum 95
  • Loading branch information
Hsbalazs authored Sep 19, 2024
2 parents 2a7f861 + 929e83a commit 178c97f
Show file tree
Hide file tree
Showing 23 changed files with 519 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
Original file line number Diff line number Diff line change
@@ -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("/pets")
public ResponseEntity<PetListResponseDto> getPets(Principal owner) {
return ResponseEntity.status(HttpStatus.OK).body(petService.getOwnerPets(owner.getName()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.greenfoxacademy.backend.dtos;

import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.PastOrPresent;
import java.util.Date;
import lombok.Data;

/**
* A data transfer object for pet details.
*
* @author Your Name
*/
@Data
public class PetDetailsDto {
@NotBlank
String name;
@NotBlank
String breed;
@NotBlank
String sex;
@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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.greenfoxacademy.backend.dtos;

import java.util.List;

/**
* A data transfer object for a list of pets.
*
*/
public record PetListResponseDto(
List<PetDetailsDto> pets
){
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
@AllArgsConstructor
@Table(name = "_owner")
public class Owner extends User {
@OneToMany(mappedBy = "petOwner")
@OneToMany(mappedBy = "owner")
private List<Pet> pets;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,6 +23,7 @@
@NoArgsConstructor
public abstract class User implements UserDetails {

@Getter
@Id
@GeneratedValue
private Integer id;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Pet, Integer> {
List<Pet> findAllByOwnerId(Integer ownerId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*/

public interface PetService {
PetListResponseDto getOwnerPets(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.greenfoxacademy.backend.services.pet;

import com.greenfoxacademy.backend.dtos.PetDetailsDto;
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<Pet> petList = petRepository
.findAllByOwnerId(ownerService.findByEmail(email).getId());

List<PetDetailsDto> petDtoList = petList.stream()
.map(pet -> modelMapper.map(pet, PetDetailsDto.class))
.collect(Collectors.toList());

return new PetListResponseDto(petDtoList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.greenfoxacademy.backend.errors.UserNotVerifiedException;
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;
Expand All @@ -22,6 +23,10 @@
*/
@Service
public interface OwnerService extends UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Owner findByEmail(String username);

RegisterResponseDto register(RegisterRequestDto userDto) throws UserAlreadyExistsError;

LoginResponseDto login(LoginRequestDto loginRequestDto)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,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;
Expand Down Expand Up @@ -106,7 +104,7 @@ public ProfileUpdateResponseDto profileUpdate(

@Cacheable(value = "profile-cache", key = "#username")
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
public Owner findByEmail(String username) throws UsernameNotFoundException {
return ownerRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("No such user!"));
}
Expand All @@ -132,4 +130,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 <code>UserDetails</code>
* 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 <code>null</code>)
* @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!"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -36,6 +37,10 @@ class EmailServiceImplTest {
@MockBean
private OwnerService ownerService;

@MockBean
private PetService petService;


private EmailServiceImpl emailService;

@BeforeEach
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.greenfoxacademy.backend.services.pet;

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;
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;

/**
* 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
public class PetServiceImplTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private PetRepository petRepository;

@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
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.setName("Morzsi");
pet1.setOwner(userWithPets);
Pet pet2 = new Pet();
pet2.setName("Rusty");
pet2.setOwner(userWithPets);

petRepository.saveAll(Arrays.asList(pet1, pet2));
}

@Test
@WithMockUser(username = "userWithPets@example.com")
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"));
}

@Test
@WithMockUser(username = "userWithNoPets@example.com")
public void testCorrectEmailWithNoExistingPets() throws Exception {
mockMvc.perform(get("/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("/pets")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is4xxClientError());
}
}
Loading

0 comments on commit 178c97f

Please sign in to comment.