Skip to content

Commit

Permalink
Merge pull request #115 from gfa-cc-after/SCRUM-96
Browse files Browse the repository at this point in the history
Scrum 96
  • Loading branch information
Hsbalazs authored Sep 24, 2024
2 parents c7dc0d7 + 741574b commit c04a69e
Show file tree
Hide file tree
Showing 18 changed files with 460 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers(allowedUrls).permitAll()
.requestMatchers("/profile-update").authenticated()
.requestMatchers("/pets").authenticated()
.requestMatchers("/add-pet").authenticated()
.anyRequest().authenticated()
)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import com.greenfoxacademy.backend.services.user.OwnerService;
import java.security.Principal;
import java.util.UUID;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.greenfoxacademy.backend.controller;

import com.greenfoxacademy.backend.dtos.AddPetResponseDto;
import com.greenfoxacademy.backend.dtos.CreatePetDto;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
Expand All @@ -19,8 +23,30 @@
public class PetController {
private final PetService petService;

/**
* Retrieves the list of pets owned by the authenticated user.
*
* @param owner the authenticated user's principal
* @return a {@link ResponseEntity} containing a {@link PetListResponseDto} with the list of pets
*/
@GetMapping("/pets")
public ResponseEntity<PetListResponseDto> getPets(Principal owner) {
return ResponseEntity.status(HttpStatus.OK).body(petService.getOwnerPets(owner.getName()));
}

/**
* Adds a new pet for the authenticated user.
*
* @param owner the authenticated user's principal
* @param createPetDto the details of the pet to be added
* @return a {@link ResponseEntity} containing an {@link AddPetResponseDto}
with the added pet's details
*/
@PostMapping("/add-pet")
public ResponseEntity<AddPetResponseDto> addPet(Principal owner,
@RequestBody CreatePetDto createPetDto) {
return ResponseEntity
.status(HttpStatus.OK)
.body(petService.addPet(owner.getName(), createPetDto));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.greenfoxacademy.backend.dtos;

/**
* A Data Transfer Object (DTO) for the response after adding a new pet.
* Contains the ID of the newly added pet.
*
* @param id the ID of the newly added pet
*/
public record AddPetResponseDto(
Integer id
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.greenfoxacademy.backend.dtos;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.PastOrPresent;
import java.util.Date;

/**
* A Data Transfer Object (DTO) for creating a new pet.
* Contains the necessary information to create a pet, including name, breed, sex, and birth date.
*
* <p>Validation constraints are applied to ensure the data integrity:
* <ul>
* <li>{@link NotBlank} ensures that the name, breed, and sex fields are not blank.</li>
* <li>{@link PastOrPresent} ensures that the birth date is not in the future.</li>
* </ul>
* </p>
*
* @param name the name of the pet, must not be blank
* @param breed the breed of the pet, must not be blank
* @param sex the sex of the pet, must not be blank
* @param birthDate the birth date of the pet, must be in the past or present
*/
public record CreatePetDto(
@NotBlank
String name,
@NotBlank
String breed,
@NotBlank
String sex,
@PastOrPresent(message = "The birth date must be in the past or present")
Date birthDate
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;

import java.util.UUID;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
Expand All @@ -31,6 +29,7 @@ public abstract class User implements UserDetails {
private Long id;
private String firstName;
private String lastName;

@Column(unique = true)
private String email;
private String password;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.greenfoxacademy.backend.services.pet;

import com.greenfoxacademy.backend.dtos.AddPetResponseDto;
import com.greenfoxacademy.backend.dtos.CreatePetDto;
import com.greenfoxacademy.backend.dtos.PetDetailsDto;
import com.greenfoxacademy.backend.dtos.PetListResponseDto;

/**
Expand All @@ -11,4 +14,6 @@

public interface PetService {
PetListResponseDto getOwnerPets(String name);

AddPetResponseDto addPet(String name, CreatePetDto createPetDto);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.greenfoxacademy.backend.services.pet;

import com.greenfoxacademy.backend.dtos.AddPetResponseDto;
import com.greenfoxacademy.backend.dtos.CreatePetDto;
import com.greenfoxacademy.backend.dtos.PetDetailsDto;
import com.greenfoxacademy.backend.dtos.PetListResponseDto;
import com.greenfoxacademy.backend.models.Pet;
Expand Down Expand Up @@ -40,4 +42,23 @@ public PetListResponseDto getOwnerPets(String email) {

return new PetListResponseDto(petDtoList);
}

/**
* Adds a new pet for the specified owner.
*
* @param email the email of the owner
* @param createPetDto the details of the pet to be added
* @return the added pet details as a PetListResponseDto
*/
@Override
public AddPetResponseDto addPet(String email, CreatePetDto createPetDto) {
Pet pet = new Pet();
pet.setName(createPetDto.name());
pet.setBreed(createPetDto.breed());
pet.setSex(createPetDto.sex());
pet.setBirthDate(createPetDto.birthDate());
pet.setOwner(ownerService.findByEmail(email));
Pet newPet = petRepository.save(pet);
return new AddPetResponseDto(newPet.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ public ProfileUpdateResponseDto profileUpdate(
return new ProfileUpdateResponseDto(authService.generateToken(updatedUser));
}

@Cacheable(value = "profile-cache", key = "#username")
@Override
public Owner findByEmail(String username) throws UsernameNotFoundException {
return ownerRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("No such user!"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.greenfoxacademy.backend.services.pet;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import com.greenfoxacademy.backend.controller.PetController;
import com.greenfoxacademy.backend.dtos.CreatePetDto;
import com.greenfoxacademy.backend.repositories.PetRepository;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import org.junit.jupiter.api.Nested;
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;

@Nested
@RequiredArgsConstructor
@ExtendWith(MockitoExtension.class)
class AddPetServiceImplTest {
@Mock
private PetService petService;

@Mock
private PetRepository petRepository;

@InjectMocks
private PetController petController;

@Test
void petSuccessfullyAddedToPetRepository() {
CreatePetDto pet = new CreatePetDto("Morzsi", "Dog", "Male", new Date(2024, 9, 13));
petService.addPet("owner", pet);

verify(petService, times(1)).addPet("owner", pet);
}
}
5 changes: 5 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "./App.css";

import { RouterProvider, createBrowserRouter } from "react-router-dom";
import AddPet from "./pages/AddPet";
import { Login } from "./pages/Login";
import { Main } from "./pages/Main";
import { PetList } from "./pages/PetList";
Expand Down Expand Up @@ -65,6 +66,10 @@ const router = createBrowserRouter([
path: "search",
element: <Search />,
},
{
path: "add-pet",
element: <AddPet />,
},
]);

function App() {
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/httpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type PetDetails = {
name: string;
breed: string;
sex: string;
birthDate: Date;
birthDate: string;
};

type PetListResponse = {
Expand All @@ -26,12 +26,33 @@ const petList = async () => {
return response.data;
};

export type CreatePet = {
name: string;
breed: string;
sex: string;
birthDate: string;
};

type AddPetResponse = {
id: number;
};

const addPet = (request: {
sex: string;
name: string;
birthDate: Date;
breed: string;
}) => {
return httpClient.post<AddPetResponse>("/add-pet", request);
};

type RegisterRequest = {
email: string;
password: string;
firstName: string;
lastName: string;
};

type RegisterResponse = {
id: number;
};
Expand Down Expand Up @@ -112,4 +133,5 @@ export {
deleteProfile,
vetList,
petList,
addPet,
};
51 changes: 51 additions & 0 deletions frontend/src/pages/AddPet.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ChakraProvider } from "@chakra-ui/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import { describe, expect, test, vi } from "vitest";
import { addPet } from "../httpClient";
import AddPet from "./AddPet";

// Mock the addPet function
vi.mock("../httpClient", () => ({
addPet: vi.fn(),
}));

describe("AddPet Component", () => {
test("renders AddPet form and submits data", async () => {
// Arrange
render(
<ChakraProvider>
<BrowserRouter>
<AddPet />
</BrowserRouter>
</ChakraProvider>,
);

// Act
fireEvent.change(screen.getByLabelText(/name/i), {
target: { value: "Buddy" },
});
fireEvent.change(screen.getByLabelText(/breed/i), {
target: { value: "Golden Retriever" },
});
fireEvent.change(screen.getByLabelText(/sex/i), {
target: { value: "Male" },
});
fireEvent.change(screen.getByLabelText(/birthDate/i), {
target: { value: "2020-01-01" },
});

fireEvent.click(screen.getByRole("button", { name: /add pet/i }));

// Assert
expect(addPet).toHaveBeenCalledWith({
name: "Buddy",
breed: "Golden Retriever",
sex: "Male",
birthDate: new Date("2020-01-01"),
});
expect(
await screen.findByText(/pet added successfully/i),
).toBeInTheDocument();
});
});
Loading

0 comments on commit c04a69e

Please sign in to comment.