Add last inference to RecipeDraftView.
This commit is contained in:
parent
d61fb97dbb
commit
77b94e6988
@ -6,6 +6,8 @@ import app.mealsmadeeasy.api.ai.OcrService;
|
|||||||
import app.mealsmadeeasy.api.auth.AuthService;
|
import app.mealsmadeeasy.api.auth.AuthService;
|
||||||
import app.mealsmadeeasy.api.auth.LoginException;
|
import app.mealsmadeeasy.api.auth.LoginException;
|
||||||
import app.mealsmadeeasy.api.recipe.body.RecipeDraftUpdateBody;
|
import app.mealsmadeeasy.api.recipe.body.RecipeDraftUpdateBody;
|
||||||
|
import app.mealsmadeeasy.api.recipe.job.RecipeInferJobHandler;
|
||||||
|
import app.mealsmadeeasy.api.recipe.view.RecipeDraftView;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserCreateException;
|
import app.mealsmadeeasy.api.user.UserCreateException;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
@ -14,17 +16,21 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mockito;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.awaitility.Awaitility.await;
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
@ -52,6 +58,12 @@ public class RecipeDraftsControllerIntegrationTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@MockitoBean
|
||||||
|
private OcrService ocrService;
|
||||||
|
|
||||||
|
@MockitoBean
|
||||||
|
private InferenceService inferenceService;
|
||||||
|
|
||||||
private static final String TEST_PASSWORD = "test";
|
private static final String TEST_PASSWORD = "test";
|
||||||
|
|
||||||
private User seedUser() {
|
private User seedUser() {
|
||||||
@ -177,12 +189,6 @@ public class RecipeDraftsControllerIntegrationTests {
|
|||||||
@Nested
|
@Nested
|
||||||
public class AiDraftTestsWithMocks {
|
public class AiDraftTestsWithMocks {
|
||||||
|
|
||||||
@MockitoBean
|
|
||||||
private OcrService ocrService;
|
|
||||||
|
|
||||||
@MockitoBean
|
|
||||||
private InferenceService inferenceService;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenAiDraft_returnsDraft() throws Exception {
|
public void whenAiDraft_returnsDraft() throws Exception {
|
||||||
final User owner = seedUser();
|
final User owner = seedUser();
|
||||||
@ -190,7 +196,7 @@ public class RecipeDraftsControllerIntegrationTests {
|
|||||||
"sourceFile",
|
"sourceFile",
|
||||||
"recipe.jpeg",
|
"recipe.jpeg",
|
||||||
MimeTypeUtils.IMAGE_JPEG_VALUE,
|
MimeTypeUtils.IMAGE_JPEG_VALUE,
|
||||||
AiDraftTestsWithMocks.class.getResourceAsStream("recipe.jpeg")
|
this.getClass().getResourceAsStream("/recipe.jpeg")
|
||||||
);
|
);
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
multipart("/recipe-drafts/ai")
|
multipart("/recipe-drafts/ai")
|
||||||
@ -205,6 +211,46 @@ public class RecipeDraftsControllerIntegrationTests {
|
|||||||
.andExpect(jsonPath("$.owner.username", is(owner.getUsername())));
|
.andExpect(jsonPath("$.owner.username", is(owner.getUsername())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pollingAfterAiDraft_returnsDraftWithInference() throws Exception {
|
||||||
|
Mockito.when(ocrService.readMarkdown(Mockito.any())).thenReturn("# Recipe");
|
||||||
|
Mockito.when(inferenceService.extract(
|
||||||
|
Mockito.any(),
|
||||||
|
Mockito.any(),
|
||||||
|
Mockito.any()
|
||||||
|
)).thenReturn(new RecipeInferJobHandler.RecipeExtraction("Recipe", List.of()));
|
||||||
|
|
||||||
|
final User owner = seedUser();
|
||||||
|
final MockMultipartFile sourceFile = new MockMultipartFile(
|
||||||
|
"sourceFile",
|
||||||
|
"recipe.jpeg",
|
||||||
|
MimeTypeUtils.IMAGE_JPEG_VALUE,
|
||||||
|
this.getClass().getResourceAsStream("/recipe.jpeg")
|
||||||
|
);
|
||||||
|
final MvcResult result = mockMvc.perform(
|
||||||
|
multipart("/recipe-drafts/ai")
|
||||||
|
.file(sourceFile)
|
||||||
|
.param("sourceFileName", sourceFile.getOriginalFilename())
|
||||||
|
.header("Authorization", "Bearer " + getAccessToken(owner))
|
||||||
|
).andReturn();
|
||||||
|
final UUID draftId = objectMapper.readValue(
|
||||||
|
result.getResponse().getContentAsString(),
|
||||||
|
RecipeDraftView.class
|
||||||
|
).id();
|
||||||
|
await().atMost(3, TimeUnit.SECONDS).untilAsserted(() -> {
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/recipe-drafts/{id}", draftId)
|
||||||
|
.header("Authorization", "Bearer " + getAccessToken(owner))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.state", is(RecipeDraft.State.ENTER_DATA.toString())))
|
||||||
|
.andExpect(jsonPath("$.lastInference", is(notNullValue())))
|
||||||
|
.andExpect(jsonPath("$.lastInference.inferredAt", is(notNullValue())))
|
||||||
|
.andExpect(jsonPath("$.lastInference.title", is("Recipe")))
|
||||||
|
.andExpect(jsonPath("$.lastInference.rawText", is("# Recipe")));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RecipeDraftUpdateBody getTestRecipeDraftUpdateBody() {
|
private static RecipeDraftUpdateBody getTestRecipeDraftUpdateBody() {
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RecipeDraftToViewConverter {
|
public class RecipeDraftToViewConverter {
|
||||||
@ -20,6 +22,17 @@ public class RecipeDraftToViewConverter {
|
|||||||
final @Nullable ImageView mainImageView = recipeDraft.getMainImage() != null
|
final @Nullable ImageView mainImageView = recipeDraft.getMainImage() != null
|
||||||
? this.imageToViewConverter.convert(recipeDraft.getMainImage(), viewer, false)
|
? this.imageToViewConverter.convert(recipeDraft.getMainImage(), viewer, false)
|
||||||
: null;
|
: null;
|
||||||
|
final @Nullable RecipeDraftView.RecipeDraftInferenceView lastInference = recipeDraft.getInferences() != null
|
||||||
|
? recipeDraft.getInferences().stream()
|
||||||
|
.max(Comparator.comparing(RecipeDraft.RecipeDraftInference::getInferredAt))
|
||||||
|
.map(inference -> RecipeDraftView.RecipeDraftInferenceView.builder()
|
||||||
|
.inferredAt(inference.getInferredAt())
|
||||||
|
.title(inference.getTitle())
|
||||||
|
.rawText(inference.getRawText())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.orElse(null)
|
||||||
|
: null;
|
||||||
return RecipeDraftView.builder()
|
return RecipeDraftView.builder()
|
||||||
.id(recipeDraft.getId())
|
.id(recipeDraft.getId())
|
||||||
.created(recipeDraft.getCreated())
|
.created(recipeDraft.getCreated())
|
||||||
@ -34,6 +47,7 @@ public class RecipeDraftToViewConverter {
|
|||||||
.ingredients(recipeDraft.getIngredients())
|
.ingredients(recipeDraft.getIngredients())
|
||||||
.owner(UserInfoView.from(recipeDraft.getOwner()))
|
.owner(UserInfoView.from(recipeDraft.getOwner()))
|
||||||
.mainImage(mainImageView)
|
.mainImage(mainImageView)
|
||||||
|
.lastInference(lastInference)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import app.mealsmadeeasy.api.recipe.RecipeDraft;
|
|||||||
import app.mealsmadeeasy.api.recipe.RecipeService;
|
import app.mealsmadeeasy.api.recipe.RecipeService;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -32,12 +33,14 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RecipeInferJobHandler implements JobHandler<RecipeInferJobPayload> {
|
public class RecipeInferJobHandler implements JobHandler<RecipeInferJobPayload> {
|
||||||
|
|
||||||
private record RecipeExtraction(
|
@ApiStatus.Internal
|
||||||
|
public record RecipeExtraction(
|
||||||
@JsonProperty(required = true) String title,
|
@JsonProperty(required = true) String title,
|
||||||
@JsonProperty(required = true) List<IngredientExtraction> ingredients
|
@JsonProperty(required = true) List<IngredientExtraction> ingredients
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private record IngredientExtraction(
|
@ApiStatus.Internal
|
||||||
|
public record IngredientExtraction(
|
||||||
String amount,
|
String amount,
|
||||||
@JsonProperty(required = true) String name,
|
@JsonProperty(required = true) String name,
|
||||||
String notes
|
String notes
|
||||||
|
|||||||
@ -24,5 +24,15 @@ public record RecipeDraftView(
|
|||||||
@Nullable String rawText,
|
@Nullable String rawText,
|
||||||
@Nullable List<RecipeDraft.IngredientDraft> ingredients,
|
@Nullable List<RecipeDraft.IngredientDraft> ingredients,
|
||||||
UserInfoView owner,
|
UserInfoView owner,
|
||||||
@Nullable ImageView mainImage
|
@Nullable ImageView mainImage,
|
||||||
) {}
|
@Nullable RecipeDraftInferenceView lastInference
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public record RecipeDraftInferenceView(
|
||||||
|
OffsetDateTime inferredAt,
|
||||||
|
String title,
|
||||||
|
String rawText
|
||||||
|
) {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user