diff --git a/.github/workflows/_test-code-samples.yml b/.github/workflows/_test-code-samples.yml index 2255ad1c6..07aa738a3 100644 --- a/.github/workflows/_test-code-samples.yml +++ b/.github/workflows/_test-code-samples.yml @@ -34,4 +34,4 @@ jobs: - name: Tests sample code run: | - ./tests/test_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} ${{ secrets.MINDEE_API_KEY_SE_TESTS }} + ./tests/test_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} ${{ secrets.MINDEE_API_KEY_SE_TESTS }} ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 2e7d4215d..98d644b95 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -33,5 +33,7 @@ jobs: env: MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }} + MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} + MINDEE_V2_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} run: | mvn clean test-compile failsafe:integration-test failsafe:verify diff --git a/src/main/java/com/mindee/MindeeClientV2.java b/src/main/java/com/mindee/MindeeClientV2.java index 5172d73e9..6a745038b 100644 --- a/src/main/java/com/mindee/MindeeClientV2.java +++ b/src/main/java/com/mindee/MindeeClientV2.java @@ -103,18 +103,6 @@ else if (resp.getJob().getStatus().equals("Processed")) { throw new RuntimeException("Max retries exceeded (" + max + ")."); } - /** - * Deserialize a webhook payload (or any saved response) into an - * {@link InferenceResponse}. - */ - public InferenceResponse loadInference(LocalResponse localResponse) throws IOException { - ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - InferenceResponse model = - mapper.readValue(localResponse.getFile(), InferenceResponse.class); - model.setRawResponse(localResponse.toString()); - return model; - } - private static MindeeApiV2 createDefaultApiV2(String apiKey) { MindeeSettingsV2 settings = apiKey == null || apiKey.trim().isEmpty() ? new MindeeSettingsV2() diff --git a/src/main/java/com/mindee/http/MindeeHttpApiV2.java b/src/main/java/com/mindee/http/MindeeHttpApiV2.java index 9a051192f..9fe0e6fcc 100644 --- a/src/main/java/com/mindee/http/MindeeHttpApiV2.java +++ b/src/main/java/com/mindee/http/MindeeHttpApiV2.java @@ -263,7 +263,7 @@ private HttpPost buildHttpPost( private R deserializeOrThrow( String body, Class clazz, int httpStatus) throws MindeeHttpExceptionV2 { - if (httpStatus >= 200 && httpStatus < 300) { + if (httpStatus >= 200 && httpStatus < 400) { try { R model = mapper.readerFor(clazz).readValue(body); model.setRawResponse(body); diff --git a/src/main/java/com/mindee/input/LocalResponse.java b/src/main/java/com/mindee/input/LocalResponse.java index 92863e684..c8d5a4abf 100644 --- a/src/main/java/com/mindee/input/LocalResponse.java +++ b/src/main/java/com/mindee/input/LocalResponse.java @@ -1,5 +1,8 @@ package com.mindee.input; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mindee.MindeeException; +import com.mindee.parsing.v2.CommonResponse; import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -91,4 +94,25 @@ public String getHmacSignature(String secretKey) { public boolean isValidHmacSignature(String secretKey, String signature) { return signature.equals(getHmacSignature(secretKey)); } + + + /** + * Deserialize this local JSON payload into a specific {@link CommonResponse} + * subtype: {@code InferenceResponse}, {@code JobResponse}. + * + * @param responseClass the concrete class to instantiate + * @param generic {@link CommonResponse} + * @return Either a {@code InferenceResponse} or {@code JobResponse} instance. + * @throws MindeeException if the payload cannot be deserialized into the requested type + */ + public T deserializeResponse(Class responseClass) { + ObjectMapper mapper = new ObjectMapper(); + try { + T response = mapper.readValue(this.file, responseClass); + response.setRawResponse(new String(this.file, StandardCharsets.UTF_8)); + return response; + } catch (Exception ex) { + throw new MindeeException("Invalid class specified for deserialization.", ex); + } + } } diff --git a/src/main/java/com/mindee/parsing/v2/field/BaseField.java b/src/main/java/com/mindee/parsing/v2/field/BaseField.java index f999d5632..c56dfa54c 100644 --- a/src/main/java/com/mindee/parsing/v2/field/BaseField.java +++ b/src/main/java/com/mindee/parsing/v2/field/BaseField.java @@ -11,7 +11,7 @@ public abstract class BaseField { * Field's location. */ @JsonProperty("locations") - private List page; + private List locations; /** * Confidence associated with the field. diff --git a/src/test/java/com/mindee/MindeeClientV2IT.java b/src/test/java/com/mindee/MindeeClientV2IT.java index 758d31f0c..58a097988 100644 --- a/src/test/java/com/mindee/MindeeClientV2IT.java +++ b/src/test/java/com/mindee/MindeeClientV2IT.java @@ -21,16 +21,6 @@ class MindeeClientV2IT { void setUp() { String apiKey = System.getenv("MINDEE_V2_API_KEY"); modelId = System.getenv("MINDEE_V2_FINDOC_MODEL_ID"); - - assumeTrue( - apiKey != null && !apiKey.trim().isEmpty(), - "MINDEE_V2_API_KEY env var is missing – integration tests skipped" - ); - assumeTrue( - modelId != null && !modelId.trim().isEmpty(), - "MINDEE_V2_FINDOC_MODEL_ID env var is missing – integration tests skipped" - ); - mindeeClient = new MindeeClientV2(apiKey); } @@ -116,7 +106,7 @@ void invalidJob_mustThrowError() { MindeeHttpExceptionV2.class, () -> mindeeClient.getInference("not-a-valid-job-ID") ); - assertEquals(404, ex.getStatus()); + assertEquals(422, ex.getStatus()); assertNotNull(ex); } } diff --git a/src/test/java/com/mindee/MindeeClientV2Test.java b/src/test/java/com/mindee/MindeeClientV2Test.java index e05fc61f5..67940ec61 100644 --- a/src/test/java/com/mindee/MindeeClientV2Test.java +++ b/src/test/java/com/mindee/MindeeClientV2Test.java @@ -114,18 +114,17 @@ void document_getInference_async() throws IOException { } @Nested - @DisplayName("loadInference()") - class LoadInference { + @DisplayName("deserializeResponse()") + class DeserializeResponse { @Test @DisplayName("parses local JSON and exposes correct field values") void inference_loadsLocally() throws IOException { - MindeeClientV2 mindeeClient = new MindeeClientV2("dummy"); File jsonFile = new File("src/test/resources/v2/products/financial_document/complete.json"); LocalResponse localResponse = new LocalResponse(jsonFile); - InferenceResponse loaded = mindeeClient.loadInference(localResponse); + InferenceResponse loaded = localResponse.deserializeResponse(InferenceResponse.class); assertNotNull(loaded, "Loaded InferenceResponse must not be null"); assertEquals( diff --git a/src/test/java/com/mindee/parsing/v2/InferenceTest.java b/src/test/java/com/mindee/parsing/v2/InferenceTest.java index df5c8f4c7..5901646d7 100644 --- a/src/test/java/com/mindee/parsing/v2/InferenceTest.java +++ b/src/test/java/com/mindee/parsing/v2/InferenceTest.java @@ -19,8 +19,8 @@ class InferenceTest { private InferenceResponse loadFromResource(String resourcePath) throws IOException { - MindeeClientV2 dummyClient = new MindeeClientV2("dummy"); - return dummyClient.loadInference(new LocalResponse(InferenceTest.class.getClassLoader().getResourceAsStream(resourcePath))); + LocalResponse localResponse = new LocalResponse(InferenceTest.class.getClassLoader().getResourceAsStream(resourcePath)); + return localResponse.deserializeResponse(InferenceResponse.class); } private String readFileAsString(String path) @@ -90,41 +90,55 @@ void asyncPredict_whenEmpty_mustHaveValidProperties() throws IOException { class CompletePrediction { @Test - @DisplayName("all properties must be valid") - void asyncPredict_whenComplete_mustHaveValidProperties() throws IOException { + @DisplayName("every exposed property must be valid and consistent") + void asyncPredict_whenComplete_mustExposeAllProperties() throws IOException { InferenceResponse response = loadFromResource("v2/products/financial_document/complete.json"); - InferenceFields fields = response.getInference().getResult().getFields(); + Inference inf = response.getInference(); + assertNotNull(inf, "Inference must not be null"); + assertEquals("12345678-1234-1234-1234-123456789abc", inf.getId(), "Inference ID mismatch"); - assertEquals(21, fields.size(), "Expected 21 fields"); + InferenceResultModel model = inf.getModel(); + assertNotNull(model, "Model must not be null"); + assertEquals("12345678-1234-1234-1234-123456789abc", model.getId(), "Model ID mismatch"); + + InferenceResultFile file = inf.getFile(); + assertNotNull(file, "File must not be null"); + assertEquals("complete.jpg", file.getName(), "File name mismatch"); + assertNull(file.getAlias(), "File alias must be null for this payload"); + + InferenceFields fields = inf.getResult().getFields(); + assertEquals(21, fields.size(), "Expected 21 fields in the payload"); + + SimpleField date = fields.get("date").getSimpleField(); + assertEquals("2019-11-02", date.getValue(), "'date' value mismatch"); DynamicField taxes = fields.get("taxes"); assertNotNull(taxes, "'taxes' field must exist"); ListField taxesList = taxes.getListField(); assertNotNull(taxesList, "'taxes' must be a ListField"); assertEquals(1, taxesList.getItems().size(), "'taxes' list must contain exactly one item"); - assertNotNull(taxes.toString(), "'taxes' toString() must not be null"); - ObjectField taxItemObj = taxesList.getItems().get(0).getObjectField(); assertNotNull(taxItemObj, "First item of 'taxes' must be an ObjectField"); assertEquals(3, taxItemObj.getFields().size(), "Tax ObjectField must contain 3 sub-fields"); - assertEquals( - 31.5, - taxItemObj.getFields().get("base").getSimpleField().getValue(), - "'taxes.base' value mismatch" - ); + SimpleField baseTax = taxItemObj.getFields().get("base").getSimpleField(); + assertEquals(31.5, baseTax.getValue(), "'taxes.base' value mismatch"); + assertNotNull(taxes.toString(), "'taxes'.toString() must not be null"); DynamicField supplierAddress = fields.get("supplier_address"); assertNotNull(supplierAddress, "'supplier_address' field must exist"); - ObjectField supplierObj = supplierAddress.getObjectField(); assertNotNull(supplierObj, "'supplier_address' must be an ObjectField"); - DynamicField country = supplierObj.getFields().get("country"); assertNotNull(country, "'supplier_address.country' must exist"); - assertEquals("USA", country.getSimpleField().getValue()); - assertEquals("USA", country.toString()); - + assertEquals("USA", country.getSimpleField().getValue(), "Country mismatch"); + assertEquals("USA", country.toString(), "'country'.toString() mismatch"); assertNotNull(supplierAddress.toString(), "'supplier_address'.toString() must not be null"); + + ObjectField customerAddr = fields.get("customer_address").getObjectField(); + SimpleField city = customerAddr.getFields().get("city").getSimpleField(); + assertEquals("New York", city.getValue(), "City mismatch"); + + assertNull(inf.getResult().getOptions(), "Options must be null"); } } @@ -206,45 +220,6 @@ void rawTexts_mustBeAccessible() throws IOException { } } - @Nested - @DisplayName("complete.json – full inference response") - class FullInference { - @Test - @DisplayName("complete financial-document JSON must round-trip correctly") - void fullInferenceResponse_mustExposeEveryProperty() throws IOException { - InferenceResponse resp = loadFromResource("v2/products/financial_document/complete.json"); - - Inference inf = resp.getInference(); - assertNotNull(inf); - assertEquals("12345678-1234-1234-1234-123456789abc", inf.getId()); - - InferenceFields f = inf.getResult().getFields(); - - SimpleField date = f.get("date").getSimpleField(); - assertEquals("2019-11-02", date.getValue()); - - ListField taxes = f.get("taxes").getListField(); - ObjectField firstTax = taxes.getItems().get(0).getObjectField(); - SimpleField baseTax = firstTax.getFields().get("base").getSimpleField(); - assertEquals(31.5, baseTax.getValue()); - - ObjectField customerAddr = f.get("customer_address").getObjectField(); - SimpleField city = customerAddr.getFields().get("city").getSimpleField(); - assertEquals("New York", city.getValue()); - - InferenceResultModel model = inf.getModel(); - assertNotNull(model); - assertEquals("12345678-1234-1234-1234-123456789abc", model.getId()); - - InferenceResultFile file = inf.getFile(); - assertNotNull(file); - assertEquals("complete.jpg", file.getName()); - assertNull(file.getAlias()); - - assertNull(inf.getResult().getOptions()); - } - } - @Nested @DisplayName("rst display") class RstDisplay {