332 lines
14 KiB
Java
332 lines
14 KiB
Java
package app.mealsmadeeasy.api.recipe;
|
|
|
|
import app.mealsmadeeasy.api.PostgresTestsExtension;
|
|
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.ImageCreateSpec;
|
|
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.web.servlet.MockMvc;
|
|
|
|
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;
|
|
|
|
@SpringBootTest
|
|
@AutoConfigureMockMvc
|
|
@ExtendWith(PostgresTestsExtension.class)
|
|
public class RecipesControllerTests {
|
|
|
|
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 = RecipeCreateSpec.builder()
|
|
.slug(UUID.randomUUID().toString())
|
|
.title("Test Recipe")
|
|
.preparationTime(10)
|
|
.cookingTime(20)
|
|
.totalTime(30)
|
|
.rawText("# Hello, World!")
|
|
.isPublic(isPublic)
|
|
.build();
|
|
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,
|
|
ImageCreateSpec.builder().build()
|
|
);
|
|
} 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.public").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 = RecipeUpdateSpec.builder()
|
|
.title("Updated Test Recipe")
|
|
.preparationTime(15)
|
|
.cookingTime(30)
|
|
.totalTime(45)
|
|
.rawText("# Hello, Updated World!")
|
|
.isPublic(true)
|
|
.build();
|
|
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.public").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 = RecipeCreateSpec.builder()
|
|
.title("Test Recipe")
|
|
.slug(UUID.randomUUID().toString())
|
|
.isPublic(false)
|
|
.rawText("# Hello, World!")
|
|
.mainImage(hal9000)
|
|
.build();
|
|
Recipe recipe = this.recipeService.create(owner, createSpec);
|
|
|
|
final RecipeUpdateSpec updateSpec = RecipeUpdateSpec.builder()
|
|
.title("Updated Test Recipe")
|
|
.rawText("# Hello, Updated World!")
|
|
.mainImage(
|
|
RecipeUpdateSpec.MainImageUpdateSpec.builder()
|
|
.username(hal9000.getOwner().getUsername())
|
|
.filename(hal9000.getUserFilename())
|
|
.build()
|
|
)
|
|
.build();
|
|
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());
|
|
}
|
|
|
|
}
|