This reverts commit b714593194.
# Conflicts:
# build.gradle
# src/integrationTest/java/app/mealsmadeeasy/api/auth/AuthControllerTests.java
345 lines
15 KiB
Java
345 lines
15 KiB
Java
package app.mealsmadeeasy.api.recipe;
|
|
|
|
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
import app.mealsmadeeasy.api.auth.AuthService;
|
|
import app.mealsmadeeasy.api.auth.LoginDetails;
|
|
import app.mealsmadeeasy.api.auth.LoginException;
|
|
import app.mealsmadeeasy.api.image.Image;
|
|
import app.mealsmadeeasy.api.image.ImageService;
|
|
import app.mealsmadeeasy.api.image.S3ImageServiceTests;
|
|
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec;
|
|
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
|
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
|
import app.mealsmadeeasy.api.user.User;
|
|
import app.mealsmadeeasy.api.user.UserCreateException;
|
|
import app.mealsmadeeasy.api.user.UserService;
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
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.test.context.DynamicPropertyRegistry;
|
|
import org.springframework.test.context.DynamicPropertySource;
|
|
import org.springframework.test.web.servlet.MockMvc;
|
|
import org.testcontainers.containers.MinIOContainer;
|
|
import org.testcontainers.junit.jupiter.Container;
|
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
|
import org.testcontainers.utility.DockerImageName;
|
|
|
|
import java.io.InputStream;
|
|
import java.util.UUID;
|
|
|
|
import static org.hamcrest.Matchers.*;
|
|
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;
|
|
|
|
@Testcontainers
|
|
@SpringBootTest
|
|
@AutoConfigureMockMvc
|
|
@ExtendWith(IntegrationTestsExtension.class)
|
|
public class RecipeControllerTests {
|
|
|
|
@Container
|
|
private static final MinIOContainer container = new MinIOContainer(
|
|
DockerImageName.parse("minio/minio:latest")
|
|
);
|
|
|
|
@DynamicPropertySource
|
|
public static void minioProperties(DynamicPropertyRegistry registry) {
|
|
registry.add("app.mealsmadeeasy.api.minio.endpoint", container::getS3URL);
|
|
registry.add("app.mealsmadeeasy.api.minio.accessKey", container::getUserName);
|
|
registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword);
|
|
}
|
|
|
|
private static InputStream getHal9000() {
|
|
return S3ImageServiceTests.class.getClassLoader().getResourceAsStream("HAL9000.svg");
|
|
}
|
|
|
|
@Autowired
|
|
private MockMvc mockMvc;
|
|
|
|
@Autowired
|
|
private RecipeService recipeService;
|
|
|
|
@Autowired
|
|
private RecipeStarService recipeStarService;
|
|
|
|
@Autowired
|
|
private UserService userService;
|
|
|
|
@Autowired
|
|
private AuthService authService;
|
|
|
|
@Autowired
|
|
private ImageService imageService;
|
|
|
|
@Autowired
|
|
private ObjectMapper objectMapper;
|
|
|
|
private static final String TEST_PASSWORD = "test";
|
|
|
|
private User seedUser() {
|
|
final String uuid = UUID.randomUUID().toString();
|
|
try {
|
|
return this.userService.createUser(uuid, uuid + "@test.com", TEST_PASSWORD);
|
|
} catch (UserCreateException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private Recipe createTestRecipe(User owner, boolean isPublic) {
|
|
final RecipeCreateSpec spec = new RecipeCreateSpec();
|
|
spec.setSlug(UUID.randomUUID().toString());
|
|
spec.setTitle("Test Recipe");
|
|
spec.setPreparationTime(10);
|
|
spec.setCookingTime(20);
|
|
spec.setTotalTime(30);
|
|
spec.setRawText("# Hello, World!");
|
|
spec.setPublic(isPublic);
|
|
return this.recipeService.create(owner, spec);
|
|
}
|
|
|
|
private String getAccessToken(User user) throws LoginException {
|
|
return this.authService.login(user.getUsername(), TEST_PASSWORD)
|
|
.getAccessToken()
|
|
.getToken();
|
|
}
|
|
|
|
private Image createHal9000(User owner) {
|
|
try (final InputStream hal9000 = getHal9000()) {
|
|
return this.imageService.create(
|
|
owner,
|
|
UUID.randomUUID() + ".svg",
|
|
hal9000,
|
|
27881L,
|
|
new ImageCreateInfoSpec()
|
|
);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void getRecipePageViewByIdPublicRecipeNoPrincipal() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
|
this.mockMvc.perform(
|
|
get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())
|
|
)
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.recipe.id").value(recipe.getId()))
|
|
.andExpect(jsonPath("$.recipe.created").exists()) // TODO: better matching of exact LocalDateTime
|
|
.andExpect(jsonPath("$.recipe.modified").doesNotExist())
|
|
.andExpect(jsonPath("$.recipe.slug").value(recipe.getSlug()))
|
|
.andExpect(jsonPath("$.recipe.title").value("Test Recipe"))
|
|
.andExpect(jsonPath("$.recipe.preparationTime").value(recipe.getPreparationTime()))
|
|
.andExpect(jsonPath("$.recipe.cookingTime").value(recipe.getCookingTime()))
|
|
.andExpect(jsonPath("$.recipe.totalTime").value(recipe.getTotalTime()))
|
|
.andExpect(jsonPath("$.recipe.text").value("<h1>Hello, World!</h1>"))
|
|
.andExpect(jsonPath("$.recipe.rawText").doesNotExist())
|
|
.andExpect(jsonPath("$.recipe.owner.id").value(owner.getId()))
|
|
.andExpect(jsonPath("$.recipe.owner.username").value(owner.getUsername()))
|
|
.andExpect(jsonPath("$.recipe.starCount").value(0))
|
|
.andExpect(jsonPath("$.recipe.viewerCount").value(0))
|
|
.andExpect(jsonPath("$.recipe.isPublic").value(true))
|
|
.andExpect(jsonPath("$.recipe.mainImage").value(nullValue()))
|
|
.andExpect(jsonPath("$.isStarred").value(nullValue()))
|
|
.andExpect(jsonPath("$.isOwner").value(nullValue()));
|
|
}
|
|
|
|
@Test
|
|
public void getFullRecipeViewIncludeRawText() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
|
this.mockMvc.perform(
|
|
get(
|
|
"/recipes/{username}/{slug}?includeRawText=true",
|
|
recipe.getOwner().getUsername(),
|
|
recipe.getSlug()
|
|
)
|
|
)
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.recipe.rawText").value(recipe.getRawText()));
|
|
}
|
|
|
|
@Test
|
|
public void getFullRecipeViewPrincipalIsStarer() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, false);
|
|
this.recipeStarService.create(recipe.getId(), owner.getId());
|
|
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.seedUser();
|
|
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
|
|
public void getRecipeInfoViewsNoPrincipal() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
|
this.mockMvc.perform(get("/recipes"))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.slice.number").value(0))
|
|
.andExpect(jsonPath("$.slice.size").value(20))
|
|
.andExpect(jsonPath("$.content").isArray())
|
|
.andExpect(jsonPath("$.content[*].id").value(hasItem(recipe.getId())));
|
|
}
|
|
|
|
@Test
|
|
public void getRecipeInfoViewsWithPrincipalIncludesPrivate() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final Recipe r0 = this.createTestRecipe(owner, true);
|
|
final Recipe r1 = this.createTestRecipe(owner, true);
|
|
final Recipe r2 = this.createTestRecipe(owner, false);
|
|
final LoginDetails loginDetails = this.authService.login(owner.getUsername(), "test");
|
|
this.mockMvc.perform(
|
|
get("/recipes")
|
|
.header("Authorization", "Bearer " + loginDetails.getAccessToken().getToken())
|
|
)
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.slice.number").value(0))
|
|
.andExpect(jsonPath("$.slice.size").value(20))
|
|
.andExpect(jsonPath("$.content").isArray())
|
|
.andExpect(jsonPath("$.content[*].id").value(hasItems(r0.getId(), r1.getId(), r2.getId())));
|
|
}
|
|
|
|
private String getUpdateBody() throws JsonProcessingException {
|
|
final RecipeUpdateSpec spec = new RecipeUpdateSpec();
|
|
spec.setTitle("Updated Test Recipe");
|
|
spec.setPreparationTime(15);
|
|
spec.setCookingTime(30);
|
|
spec.setTotalTime(45);
|
|
spec.setRawText("# Hello, Updated World!");
|
|
spec.setIsPublic(true);
|
|
return this.objectMapper.writeValueAsString(spec);
|
|
}
|
|
|
|
@Test
|
|
public void updateRecipe() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, false);
|
|
final String accessToken = this.getAccessToken(owner);
|
|
final String body = this.getUpdateBody();
|
|
this.mockMvc.perform(
|
|
post("/recipes/{username}/{slug}", owner.getUsername(), recipe.getSlug())
|
|
.header("Authorization", "Bearer " + accessToken)
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content(body)
|
|
)
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.recipe.id").value(recipe.getId()))
|
|
.andExpect(jsonPath("$.recipe.title").value("Updated Test Recipe"))
|
|
.andExpect(jsonPath("$.recipe.preparationTime").value(15))
|
|
.andExpect(jsonPath("$.recipe.cookingTime").value(30))
|
|
.andExpect(jsonPath("$.recipe.totalTime").value(45))
|
|
.andExpect(jsonPath("$.recipe.text").value("<h1>Hello, Updated World!</h1>"))
|
|
.andExpect(jsonPath("$.recipe.rawText").value("# Hello, Updated World!"))
|
|
.andExpect(jsonPath("$.recipe.owner.id").value(owner.getId()))
|
|
.andExpect(jsonPath("$.recipe.owner.username").value(owner.getUsername()))
|
|
.andExpect(jsonPath("$.recipe.starCount").value(0))
|
|
.andExpect(jsonPath("$.recipe.viewerCount").value(0))
|
|
.andExpect(jsonPath("$.recipe.isPublic").value(true))
|
|
.andExpect(jsonPath("$.recipe.mainImage").value(nullValue()))
|
|
.andExpect(jsonPath("$.isStarred").value(false))
|
|
.andExpect(jsonPath("$.isOwner").value(true));
|
|
}
|
|
|
|
@Test
|
|
public void updateRecipeReturnsViewWithMainImage() throws Exception {
|
|
final User owner = this.seedUser();
|
|
|
|
final Image hal9000 = this.createHal9000(owner);
|
|
|
|
final RecipeCreateSpec createSpec = new RecipeCreateSpec();
|
|
createSpec.setTitle("Test Recipe");
|
|
createSpec.setSlug("test-recipe");
|
|
createSpec.setPublic(false);
|
|
createSpec.setRawText("# Hello, World!");
|
|
createSpec.setMainImage(hal9000);
|
|
Recipe recipe = this.recipeService.create(owner, createSpec);
|
|
|
|
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec();
|
|
updateSpec.setTitle("Updated Test Recipe");
|
|
updateSpec.setRawText("# Hello, Updated World!");
|
|
final RecipeUpdateSpec.MainImageUpdateSpec mainImageUpdateSpec = new RecipeUpdateSpec.MainImageUpdateSpec();
|
|
mainImageUpdateSpec.setUsername(hal9000.getOwner().getUsername());
|
|
mainImageUpdateSpec.setFilename(hal9000.getUserFilename());
|
|
updateSpec.setMainImage(mainImageUpdateSpec);
|
|
final String body = this.objectMapper.writeValueAsString(updateSpec);
|
|
|
|
final String accessToken = this.getAccessToken(owner);
|
|
this.mockMvc.perform(
|
|
post("/recipes/{username}/{slug}", owner.getUsername(), recipe.getSlug())
|
|
.header("Authorization", "Bearer " + accessToken)
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content(body)
|
|
)
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.recipe.mainImage").isMap());
|
|
}
|
|
|
|
@Test
|
|
public void addStarToRecipe() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final User starer = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
|
this.mockMvc.perform(
|
|
post("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
|
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
|
)
|
|
.andExpect(status().isCreated())
|
|
.andExpect(jsonPath("$.timestamp").exists());
|
|
}
|
|
|
|
@Test
|
|
public void getStarForRecipe() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final User starer = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
|
this.recipeStarService.create(recipe.getId(), starer.getId());
|
|
this.mockMvc.perform(
|
|
get("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
|
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
|
)
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.isStarred").value(true))
|
|
.andExpect(jsonPath("$.star").isMap())
|
|
.andExpect(jsonPath("$.star.timestamp").exists());
|
|
}
|
|
|
|
@Test
|
|
public void deleteStarFromRecipe() throws Exception {
|
|
final User owner = this.seedUser();
|
|
final User starer = this.seedUser();
|
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
|
this.recipeStarService.create(recipe.getId(), starer.getId());
|
|
this.mockMvc.perform(
|
|
delete("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
|
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
|
)
|
|
.andExpect(status().isNoContent());
|
|
}
|
|
|
|
}
|