diff --git a/src/main/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeController.java b/src/main/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeController.java index 949c77d9592170627dd5313ac8a02f8f54ccc69e..39329cbdd288080ec783171370cb7e2e6e313ccd 100644 --- a/src/main/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeController.java +++ b/src/main/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeController.java @@ -1,21 +1,27 @@ package ntnu.idatt2016.v233.SmartMat.controller.group; import lombok.AllArgsConstructor; +import ntnu.idatt2016.v233.SmartMat.dto.enums.Authority; import ntnu.idatt2016.v233.SmartMat.dto.request.FridgeProductRequest; import ntnu.idatt2016.v233.SmartMat.entity.fridgeProduct.FridgeProductAsso; import ntnu.idatt2016.v233.SmartMat.entity.group.Fridge; import ntnu.idatt2016.v233.SmartMat.entity.product.Product; import ntnu.idatt2016.v233.SmartMat.service.group.FridgeService; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.web.bind.annotation.*; +import java.util.Optional; + /** * Controller for fridges API, providing endpoints for fridge management * - * @author Anders Austlid - * @version 1.0 - * @since 24.04.2023 + * @author Anders Austlid & Birk + * @version 2.0 + * @since 3.05.2023 */ @AllArgsConstructor @RestController @@ -24,27 +30,42 @@ public class FridgeController { private final FridgeService fridgeService; + /** * Gets the fridge of a group - * @param groupId the id of the group - * group must exist - * @return the fridge of the group if it exists, or a 404 if it doesn't + * @param groupId the id of the group must exist + * @return the fridge of the group if it exists, or a 404 if it doesn't exist or the user is not in the group */ @GetMapping("/group/{groupId}") - public ResponseEntity<Fridge> getFridgeByGroupId(@PathVariable("groupId") long groupId) { - return fridgeService.getFridgeByGroupId(groupId) - .map(ResponseEntity::ok) - .orElseGet(() -> ResponseEntity.notFound().build()); + public ResponseEntity<Fridge> getFridgeByGroupId(@PathVariable("groupId") long groupId, Authentication authentication) { + Optional<Fridge> fridge = fridgeService.getFridgeByGroupId(groupId); + + if (fridge.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + if (!fridgeService.isUserInFridge(authentication.getName(), fridge.get().getFridgeId()) + && !authentication.getAuthorities().contains(new SimpleGrantedAuthority(Authority.ADMIN.name()))) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + return fridge.map(ResponseEntity::ok).get(); } /** * Gets the fridge by its fridge id * @param fridgeId the id of the fridge - * @return the fridge if it exists, or a 404 if it doesn't + * @return the fridge if it exists, or a 404 if it doesn't, or a 403 if the user is not in the fridge */ @GetMapping("/fridge/{fridgeId}") - public ResponseEntity<Fridge> getFridgeByFridgeId(@PathVariable("fridgeId") long fridgeId) { + public ResponseEntity<Fridge> getFridgeByFridgeId(@PathVariable("fridgeId") long fridgeId, + Authentication authentication) { + if (!fridgeService.isUserInFridge(authentication.getName(), fridgeId) + && !authentication.getAuthorities().contains(new SimpleGrantedAuthority(Authority.ADMIN.name()))) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + return fridgeService.getFridgeByFridgeId(fridgeId) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); @@ -58,22 +79,69 @@ public class FridgeController { * @return success if the product was added, bad request if the product was already in the fridge, or not found if the group or product doesn't exist */ @PostMapping("/group/product") - public ResponseEntity<Product> addProductToFridge(@RequestBody FridgeProductRequest request) { + public ResponseEntity<Product> addProductToFridge(@RequestBody FridgeProductRequest request, + Authentication authentication) { + + Optional<Fridge> fridge = fridgeService.getFridgeByGroupId(request.groupId()); + + if (fridge.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + if (!fridgeService.isUserInFridge(authentication.getName(), fridge.get().getFridgeId()) && + !authentication.getAuthorities().contains(new SimpleGrantedAuthority(Authority.ADMIN.name()))) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + try { - return fridgeService.addProductToFridge(request).map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + return fridgeService.addProductToFridge(request).map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().build(); } } + /** + * Updates a product in a fridge + * @param request the request containing the group id and product id + * @return success if the product was added, bad request if the product was already in the fridge, + * or not found if the group or product doesn't exist + */ @PutMapping("/group/product") - public ResponseEntity<FridgeProductAsso> updateProductInFridge(@RequestBody FridgeProductRequest request) { + public ResponseEntity<FridgeProductAsso> updateProductInFridge(@RequestBody FridgeProductRequest request, + Authentication authentication) { + Optional<Fridge> fridge = fridgeService.getFridgeByGroupId(request.groupId()); + + if (fridge.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + if (!fridgeService.isUserInFridge(authentication.getName(), fridge.get().getFridgeId()) && + !authentication.getAuthorities().contains(new SimpleGrantedAuthority(Authority.ADMIN.name()))) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + return fridgeService.updateProductInFridge(request).map(ResponseEntity::ok).orElseGet(()-> ResponseEntity.notFound().build()); } + /** + * Deletes an amount of a product from a fridge + * @param fridgeProductId the id of the fridge product to delete + * @param amountStr the amount to delete + * @param authentication the authentication of the user + * @return 200 if the amount was deleted, 404 if the fridge product doesn't exist, 403 if the user is not in the group + */ @DeleteMapping("/group/delete/product/{fridgeProductId}/{amount}") public ResponseEntity<?> deleteAmountFridgeProduct(@PathVariable("fridgeProductId") long fridgeProductId, - @PathVariable("amount") String amountStr) { + @PathVariable("amount") String amountStr, Authentication authentication) { + + + if (!fridgeService.isUserInGroupWithFridgeProduct( authentication.getName(), fridgeProductId) + && !authentication.getAuthorities().contains(new SimpleGrantedAuthority(Authority.ADMIN.name()))){ + return ResponseEntity.status(403).body("You are not a member of this group"); + } + try { double amount = Double.parseDouble(amountStr); @@ -81,7 +149,7 @@ public class FridgeController { return ResponseEntity.badRequest().body("Amount must be greater than or equal to 0."); } - return fridgeService.deleteAmountFromFridge(fridgeProductId, amount).map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + return ResponseEntity.ok(fridgeService.deleteAmountFromFridge(fridgeProductId, amount)); } catch (NumberFormatException e) { return ResponseEntity.badRequest().body("Invalid amount format. Please provide a valid number."); } @@ -93,15 +161,22 @@ public class FridgeController { * Deletes a product from the fridge * @param fridgeProductId the id of the fridge product association * @return success if the product was deleted, bad request if the product wasn't found + * , or forbidden if the user is not in the group */ @DeleteMapping("/delete/product/{fridgeProductId}") - public ResponseEntity<String> removeProductFromFridge(@PathVariable("fridgeProductId") long fridgeProductId) { + public ResponseEntity<String> removeProductFromFridge(@PathVariable("fridgeProductId") long fridgeProductId, + Authentication authentication) { + + if (!fridgeService.isUserInGroupWithFridgeProduct( authentication.getName(), fridgeProductId) + && !authentication.getAuthorities().contains(new SimpleGrantedAuthority(Authority.ADMIN.name()))){ + return ResponseEntity.status(403).body("You are not a member of this group"); + } + try { - boolean success = fridgeService.removeProductFromFridge(fridgeProductId); - if (success){ - return ResponseEntity.ok("Success"); - } - return ResponseEntity.badRequest().body("Product not found in the fridge"); + return (fridgeService.removeProductFromFridge(fridgeProductId)) ? + ResponseEntity.ok("Product removed from fridge") + : ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product not found in the fridge"); + } catch (Exception e) { return ResponseEntity.status(500).body("Internal server error"); } @@ -111,11 +186,20 @@ public class FridgeController { * Deletes a product from the fridge and creates a waste object from it. * * @param fridgeProductId The id of the fridge product association to be deleted - * @return A ResponseEntity with status code 200 if successful, or status code 404 if the specified fridge product association was not found. + * @return A ResponseEntity with status code 200 if successful, + * or status code 404 if the specified fridge product association was not found. + * or status code 403 if the user is not in the group */ @DeleteMapping("/waste/product/{fridgeProductId}") - public ResponseEntity<?> wasteProductFromFridge(@PathVariable("fridgeProductId") long fridgeProductId){ - return fridgeService.wasteProductFromFridge(fridgeProductId).map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + public ResponseEntity<?> wasteProductFromFridge(@PathVariable("fridgeProductId") long fridgeProductId, + Authentication authentication){ + if (!fridgeService.isUserInGroupWithFridgeProduct( authentication.getName(), fridgeProductId) + && !authentication.getAuthorities().contains(new SimpleGrantedAuthority(Authority.ADMIN.name()))){ + return ResponseEntity.status(403).body("You are not a member of this group"); + } + + return fridgeService.wasteProductFromFridge(fridgeProductId) + .map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); } diff --git a/src/main/java/ntnu/idatt2016/v233/SmartMat/service/group/FridgeService.java b/src/main/java/ntnu/idatt2016/v233/SmartMat/service/group/FridgeService.java index 71d13e004a416492de41e3bb081b1edc4250c5b5..f04bcb487e2d9ae268d9aca92517676f841a4890 100644 --- a/src/main/java/ntnu/idatt2016/v233/SmartMat/service/group/FridgeService.java +++ b/src/main/java/ntnu/idatt2016/v233/SmartMat/service/group/FridgeService.java @@ -212,15 +212,27 @@ public class FridgeService { /** - * Delete all products in a fridge + * Get all the fridge products of a group + * @param username the username of the user + * @param fridgeProductId the id of the fridge product + * @return true if the user is in the group of the fridge product + */ + public boolean isUserInGroupWithFridgeProduct(String username, long fridgeProductId) { + Optional<Fridge> fridge = fridgeProductAssoRepo.findById(fridgeProductId) + .map(FridgeProductAsso::getFridgeId); + return fridge.map(value -> value.getGroup().getUser().stream() + .anyMatch(user -> user.getUser().getUsername().equals(username))).orElse(false); + } + + /** + * check if user has accsess to fridge + * @param username the username of the user * @param fridgeId the id of the fridge - * @return true if the fridge was deleted - * - public boolean deleteAllProductsInFridge(long fridgeId) { + * @return true if the user is in the group of the fridge + */ + public boolean isUserInFridge(String username, long fridgeId) { Optional<Fridge> fridge = fridgeRepository.findById(fridgeId); - if(fridge.isEmpty()) return false; - fridgeProductAssoService.deleteAllFridgeProducts(fridgeId); - return true; + return fridge.map(value -> value.getGroup().getUser().stream() + .anyMatch(user -> user.getUser().getUsername().equals(username))).orElse(false); } - */ } diff --git a/src/test/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeControllerTest.java b/src/test/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeControllerTest.java index 307be4db9141b3099e236fc6f67d7bc84f99f643..418ef509f48c3a13f2da69ffb80655f6129e6a0a 100644 --- a/src/test/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeControllerTest.java +++ b/src/test/java/ntnu/idatt2016/v233/SmartMat/controller/group/FridgeControllerTest.java @@ -1,6 +1,7 @@ package ntnu.idatt2016.v233.SmartMat.controller.group; import com.fasterxml.jackson.databind.ObjectMapper; +import ntnu.idatt2016.v233.SmartMat.dto.enums.Authority; import ntnu.idatt2016.v233.SmartMat.dto.request.FridgeProductRequest; import ntnu.idatt2016.v233.SmartMat.entity.fridgeProduct.FridgeProductAsso; import ntnu.idatt2016.v233.SmartMat.entity.group.Fridge; @@ -9,40 +10,122 @@ import ntnu.idatt2016.v233.SmartMat.service.group.FridgeService; 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.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.Collection; +import java.util.List; import java.util.Optional; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -@ExtendWith(SpringExtension.class) -@WebMvcTest(FridgeController.class) -@AutoConfigureMockMvc(addFilters = false) +@ExtendWith(MockitoExtension.class) public class FridgeControllerTest { - @Autowired - private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; - @MockBean + @InjectMocks + private FridgeController fridgeController; + @Mock private FridgeService fridgeService; + private Fridge fridge; private Product product; private FridgeProductAsso fridgeProductAsso; private FridgeProductRequest fridgeProductRequest; + + private Authentication regularUser = new Authentication() { + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return List.of(new SimpleGrantedAuthority(Authority.USER.name())); + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + + } + + @Override + public String getName() { + return "test"; + } + }; + + private Authentication adminUser = new Authentication() { + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return List.of(new SimpleGrantedAuthority(Authority.ADMIN.name())); + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + + } + + @Override + public String getName() { + return "test"; + } + }; + + @BeforeEach public void setUp() { // Create and set up your test data here @@ -53,92 +136,171 @@ public class FridgeControllerTest { } @Test - public void getFridgeByGroupId() throws Exception { + public void getFridgeByGroupIdAsAdmin() throws Exception { when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.of(fridge)); - mockMvc.perform(get("/api/fridges/group/1")) - .andExpect(status().isOk()); + + ResponseEntity<Fridge> responseEntity = fridgeController.getFridgeByGroupId(1L, adminUser); + + + verify(fridgeService).getFridgeByGroupId(1L); + assertEquals(responseEntity.getStatusCode(), HttpStatus.OK); + + } + + @Test + public void getFridgeByGroupIdAsUser() throws Exception { + when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.of(fridge)); + + ResponseEntity<Fridge> responseEntity = fridgeController.getFridgeByGroupId(1L, regularUser); + + verify(fridgeService).getFridgeByGroupId(1L); + + assertEquals(responseEntity.getStatusCode(), HttpStatus.FORBIDDEN); + } @Test public void getFridgeByGroupId_notFound() throws Exception { when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.empty()); - mockMvc.perform(get("/api/fridges/group/1")) - .andExpect(status().isNotFound()); + ResponseEntity<Fridge> responseEntity = fridgeController.getFridgeByGroupId(1L, adminUser); + + + verify(fridgeService).getFridgeByGroupId(1L); + assertEquals(responseEntity.getStatusCode(), HttpStatus.NOT_FOUND); } @Test public void getFridgeByFridgeId() throws Exception { when(fridgeService.getFridgeByFridgeId(1L)).thenReturn(Optional.of(fridge)); - mockMvc.perform(get("/api/fridges/fridge/1")) - .andExpect(status().isOk()); + ResponseEntity<Fridge> responseEntity = fridgeController.getFridgeByFridgeId(1L, adminUser); + + + verify(fridgeService).getFridgeByFridgeId(1L); + assertEquals(responseEntity.getStatusCode(), HttpStatus.OK); + } + + @Test + public void getFridgeByFridgeIdAsUser() throws Exception { + + ResponseEntity<Fridge> responseEntity = fridgeController.getFridgeByFridgeId(1L, regularUser); + + verify(fridgeService).isUserInFridge("test", 1L); + + + assertEquals(responseEntity.getStatusCode(), HttpStatus.FORBIDDEN); + } @Test public void getFridgeByFridgeId_notFound() throws Exception { when(fridgeService.getFridgeByFridgeId(1L)).thenReturn(Optional.empty()); - mockMvc.perform(get("/api/fridges/fridge/1")) - .andExpect(status().isNotFound()); + ResponseEntity<Fridge> responseEntity = fridgeController.getFridgeByFridgeId(1L, adminUser); + + + verify(fridgeService).getFridgeByFridgeId(1L); + assertEquals(responseEntity.getStatusCode(), HttpStatus.NOT_FOUND); } @Test public void addProductToFridge() throws Exception { when(fridgeService.addProductToFridge(any(FridgeProductRequest.class))).thenReturn(Optional.of(product)); - mockMvc.perform(post("/api/fridges/group/product") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(fridgeProductRequest))) - .andExpect(status().isOk()); + when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.of(fridge)); + + ResponseEntity<Product> responseEntity = fridgeController.addProductToFridge(fridgeProductRequest, adminUser); + + + verify(fridgeService).addProductToFridge(any(FridgeProductRequest.class)); + + assertEquals(responseEntity.getStatusCode(), HttpStatus.OK); + + + } + + @Test + public void addProductToFridgeAsUser() throws Exception { + + when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.of(fridge)); + + + ResponseEntity<Product> responseEntity = fridgeController.addProductToFridge(fridgeProductRequest, regularUser); + + verify(fridgeService).isUserInFridge("test", 0L); + + assertEquals(responseEntity.getStatusCode(), HttpStatus.FORBIDDEN); + } @Test public void addProductToFridge_notFound() throws Exception { - when(fridgeService.addProductToFridge(any(FridgeProductRequest.class))).thenReturn(Optional.empty()); + when(fridgeService.addProductToFridge(any(FridgeProductRequest.class))).thenReturn(Optional.empty( )); + + when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.of(fridge)); + + + ResponseEntity<Product> responseEntity = fridgeController.addProductToFridge(fridgeProductRequest, adminUser); + + verify(fridgeService).isUserInFridge("test", 0L); - mockMvc.perform(post("/api/fridges/group/product") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(fridgeProductRequest))) - .andExpect(status().isNotFound()); + assertEquals(responseEntity.getStatusCode(), HttpStatus.NOT_FOUND); } @Test public void updateProductInFridge() throws Exception { when(fridgeService.updateProductInFridge(any(FridgeProductRequest.class))).thenReturn(Optional.of(fridgeProductAsso)); - mockMvc.perform(put("/api/fridges/group/product") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(fridgeProductRequest))) - .andExpect(status().isOk()); + when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.of(fridge)); + ResponseEntity<FridgeProductAsso> responseEntity = + fridgeController.updateProductInFridge(fridgeProductRequest, adminUser); + + + verify(fridgeService).updateProductInFridge(any(FridgeProductRequest.class)); + + assertEquals(responseEntity.getStatusCode(), HttpStatus.OK); } @Test public void updateProductInFridge_notFound() throws Exception { when(fridgeService.updateProductInFridge(any(FridgeProductRequest.class))).thenReturn(Optional.empty()); - mockMvc.perform(put("/api/fridges/group/product") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(fridgeProductRequest))) - .andExpect(status().isNotFound()); + when(fridgeService.getFridgeByGroupId(1L)).thenReturn(Optional.of(fridge)); + ResponseEntity<FridgeProductAsso> responseEntity = + fridgeController.updateProductInFridge(fridgeProductRequest, adminUser); + + + verify(fridgeService).updateProductInFridge(any(FridgeProductRequest.class)); + + assertEquals(responseEntity.getStatusCode(), HttpStatus.NOT_FOUND); } @Test public void removeProductFromFridge_success() throws Exception { when(fridgeService.removeProductFromFridge(1L)).thenReturn(true); - mockMvc.perform(delete("/api/fridges/delete/product/1")) - .andExpect(status().isOk()) - .andExpect(content().string("Success")); + + ResponseEntity<String> responseEntity = + fridgeController.removeProductFromFridge(1L, adminUser); + + verify(fridgeService).removeProductFromFridge(1L); + + assertEquals(responseEntity.getStatusCode(), HttpStatus.OK); + + } @Test - public void removeProductFromFridge_badRequest() throws Exception { + public void removeProductFromFridge_notFound() throws Exception { when(fridgeService.removeProductFromFridge(1L)).thenReturn(false); - mockMvc.perform(delete("/api/fridges/delete/product/1")) - .andExpect(status().isBadRequest()) - .andExpect(content().string("Product not found in the fridge")); + ResponseEntity<String> responseEntity = + fridgeController.removeProductFromFridge(1L, adminUser); + + verify(fridgeService).removeProductFromFridge(1L); + + assertEquals(responseEntity.getStatusCode(), HttpStatus.NOT_FOUND); } }