From 73fdced1312bef0d5a64c1bd7e3470d1de396afc Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Wed, 14 Aug 2024 08:46:42 -0500 Subject: [PATCH] Added starred to FullRecipeView and related query/service implementation. --- .../api/recipe/RecipeControllerTests.java | 34 ++++++++- .../api/recipe/RecipeServiceTests.java | 42 +++++++++++ .../star/RecipeStarRepositoryTests.java | 71 +++++++++++++++++++ .../api/recipe/RecipeServiceImpl.java | 13 +++- .../api/recipe/star/RecipeStarRepository.java | 3 + .../api/recipe/view/FullRecipeView.java | 11 +++ 6 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java index 09b2fe1..32f2f70 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java @@ -16,6 +16,7 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.nullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -74,7 +75,9 @@ public class RecipeControllerTests { public void getRecipePageViewByIdPublicRecipeNoPrincipal() throws Exception { final User owner = this.createTestUser("owner"); final Recipe recipe = this.createTestRecipe(owner, true); - this.mockMvc.perform(get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())) + this.mockMvc.perform( + get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug()) + ) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.created").exists()) // TODO: better matching of exact LocalDateTime @@ -88,10 +91,39 @@ public class RecipeControllerTests { .andExpect(jsonPath("$.owner.id").value(owner.getId())) .andExpect(jsonPath("$.owner.username").value(owner.getUsername())) .andExpect(jsonPath("$.starCount").value(0)) + .andExpect(jsonPath("$.isStarred").value(nullValue())) .andExpect(jsonPath("$.viewerCount").value(0)) .andExpect(jsonPath("$.isPublic").value(true)); } + @Test + @DirtiesContext + public void getFullRecipeViewPrincipalIsStarer() throws Exception { + final User owner = this.createTestUser("owner"); + final Recipe recipe = this.createTestRecipe(owner, false); + this.recipeStarService.create(recipe.getId(), owner.getUsername()); + final String accessToken = this.getAccessToken(owner); + this.mockMvc.perform( + get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug()) + .header("Authorization", "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isStarred").value(true)); + } + + @Test + public void getFullRecipeViewPrincipalIsNotStarer() throws Exception { + final User owner = this.createTestUser("owner"); + final Recipe recipe = this.createTestRecipe(owner, false); + final String accessToken = this.getAccessToken(owner); + this.mockMvc.perform( + get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug()) + .header("Authorization", "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isStarred").value(false)); + } + @Test @DirtiesContext public void getRecipeInfoViewsNoPrincipal() throws Exception { diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java index 9ec3f9c..a38fe98 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java @@ -4,6 +4,7 @@ import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.star.RecipeStar; import app.mealsmadeeasy.api.recipe.star.RecipeStarService; +import app.mealsmadeeasy.api.recipe.view.FullRecipeView; import app.mealsmadeeasy.api.recipe.view.RecipeInfoView; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserEntity; @@ -23,6 +24,7 @@ import static app.mealsmadeeasy.api.recipe.ContainsRecipeInfoViewsForRecipesMatc import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -350,4 +352,44 @@ public class RecipeServiceTests { assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete.getId(), notOwner)); } + @Test + @DirtiesContext + public void getFullViewByUsernameAndSlugIncludesStarredFalseWhenViewerNotNullButNotStarer() throws RecipeException { + final User owner = this.createTestUser("recipeOwner"); + final Recipe recipe = this.createTestRecipe(owner); + final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug( + owner.getUsername(), + recipe.getSlug(), + owner + ); + assertThat(view.getIsStarred(), is(false)); + } + + @Test + @DirtiesContext + public void getFullViewByUsernameAndSlugIncludesStarredTrueWhenViewerNotNullAndStarer() throws RecipeException { + final User owner = this.createTestUser("recipeOwner"); + final Recipe recipe = this.createTestRecipe(owner); + this.recipeStarService.create(recipe.getId(), owner.getUsername()); + final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug( + owner.getUsername(), + recipe.getSlug(), + owner + ); + assertThat(view.getIsStarred(), is(true)); + } + + @Test + @DirtiesContext + public void getFullViewByUsernameIncludesStarredNullWhenViewerNull() throws RecipeException { + final User owner = this.createTestUser("recipeOwner"); + final Recipe recipe = this.createTestRecipe(owner, true); + final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug( + owner.getUsername(), + recipe.getSlug(), + null + ); + assertThat(view.getIsStarred(), is(nullValue())); + } + } diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java new file mode 100644 index 0000000..0f14670 --- /dev/null +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java @@ -0,0 +1,71 @@ +package app.mealsmadeeasy.api.recipe.star; + +import app.mealsmadeeasy.api.recipe.RecipeEntity; +import app.mealsmadeeasy.api.recipe.RecipeRepository; +import app.mealsmadeeasy.api.user.UserEntity; +import app.mealsmadeeasy.api.user.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import java.time.LocalDateTime; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@SpringBootTest +public class RecipeStarRepositoryTests { + + @Autowired + private RecipeStarRepository recipeStarRepository; + + @Autowired + private RecipeRepository recipeRepository; + + @Autowired + private UserRepository userRepository; + + private UserEntity getOwnerUser() { + final UserEntity draft = UserEntity.getDefaultDraft(); + draft.setUsername("test-user"); + draft.setEmail("test-user@test.com"); + draft.setPassword("test"); + return this.userRepository.save(draft); + } + + private RecipeEntity getTestRecipe(UserEntity owner) { + final RecipeEntity recipeDraft = new RecipeEntity(); + recipeDraft.setCreated(LocalDateTime.now()); + recipeDraft.setSlug("test-recipe"); + recipeDraft.setOwner(owner); + recipeDraft.setTitle("Test Recipe"); + recipeDraft.setRawText("Hello, World!"); + return this.recipeRepository.save(recipeDraft); + } + + @Test + @DirtiesContext + public void returnsTrueIfStarer() { + final UserEntity owner = this.getOwnerUser(); + final RecipeEntity recipe = this.getTestRecipe(owner); + + final RecipeStarEntity starDraft = new RecipeStarEntity(); + final RecipeStarId starId = new RecipeStarId(); + starId.setRecipeId(recipe.getId()); + starId.setOwnerUsername(owner.getUsername()); + starDraft.setId(starId); + this.recipeStarRepository.save(starDraft); + + assertThat(this.recipeStarRepository.isStarer(recipe.getId(), owner.getUsername()), is(true)); + } + + @Test + @DirtiesContext + public void returnsFalseIfNotStarer() { + final UserEntity owner = this.getOwnerUser(); + final RecipeEntity recipe = this.getTestRecipe(owner); + assertThat(this.recipeStarRepository.isStarer(recipe.getId(), owner.getUsername()), is(false)); + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java index 91ba888..3ca6cd6 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java @@ -6,6 +6,7 @@ import app.mealsmadeeasy.api.image.S3ImageEntity; import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; +import app.mealsmadeeasy.api.recipe.star.RecipeStarRepository; import app.mealsmadeeasy.api.recipe.view.FullRecipeView; import app.mealsmadeeasy.api.recipe.view.RecipeInfoView; import app.mealsmadeeasy.api.user.User; @@ -40,11 +41,16 @@ public class RecipeServiceImpl implements RecipeService { } private final RecipeRepository recipeRepository; + private final RecipeStarRepository recipeStarRepository; private final ImageService imageService; - - public RecipeServiceImpl(RecipeRepository recipeRepository, ImageService imageService) { + public RecipeServiceImpl( + RecipeRepository recipeRepository, + RecipeStarRepository recipeStarRepository, + ImageService imageService + ) { this.recipeRepository = recipeRepository; + this.recipeStarRepository = recipeStarRepository; this.imageService = imageService; } @@ -124,6 +130,9 @@ public class RecipeServiceImpl implements RecipeService { recipe, this.getRenderedMarkdown(recipe), this.getStarCount(recipe), + viewer != null + ? this.recipeStarRepository.isStarer(recipe.getId(), viewer.getUsername()) + : null, this.getViewerCount(recipe.getId()), this.getImageView(recipe.getMainImage(), viewer) ); diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java index 884cad1..1d160b1 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java @@ -12,6 +12,9 @@ public interface RecipeStarRepository extends JpaRepository findByRecipeIdAndOwnerUsername(Long recipeId, String username); + @Query("SELECT count(rs) > 0 FROM RecipeStar rs WHERE rs.id.recipeId = ?1 AND rs.id.ownerUsername = ?2") + boolean isStarer(long recipeId, String username); + @Modifying @Transactional @Query("DELETE FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2") diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java index c64278f..e91009d 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java @@ -13,6 +13,7 @@ public class FullRecipeView { Recipe recipe, String renderedText, int starCount, + Boolean starred, int viewerCount, ImageView mainImage ) { @@ -28,6 +29,7 @@ public class FullRecipeView { view.setText(renderedText); view.setOwner(UserInfoView.from(recipe.getOwner())); view.setStarCount(starCount); + view.setIsStarred(starred); view.setViewerCount(viewerCount); view.setMainImage(mainImage); view.setIsPublic(recipe.isPublic()); @@ -45,6 +47,7 @@ public class FullRecipeView { private String text; private UserInfoView owner; private int starCount; + private @Nullable Boolean starred; private int viewerCount; private ImageView mainImage; private boolean isPublic; @@ -137,6 +140,14 @@ public class FullRecipeView { this.starCount = starCount; } + public @Nullable Boolean getIsStarred() { + return this.starred; + } + + public void setIsStarred(@Nullable Boolean starred) { + this.starred = starred; + } + public int getViewerCount() { return this.viewerCount; }