Skip to content

Commit 1c3a4fb

Browse files
add tests
1 parent 9eb38d6 commit 1c3a4fb

12 files changed

Lines changed: 623 additions & 19 deletions

.github/workflows/_test-code-samples.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ jobs:
3030
env:
3131
MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }}
3232
run: |
33-
./tests/test_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} ${{ secrets.MINDEE_API_KEY_SE_TESTS }}
33+
./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 }}

.github/workflows/_test-integrations.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ env:
1212
MINDEE_ENDPOINT_SE_TESTS: ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }}
1313
MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }}
1414
WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }}
15+
MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }}
16+
MINDEE_V2_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }}
1517

1618
jobs:
1719
integration-tests-ubuntu:

.github/workflows/pull-request.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@ name: Pull Request
33
on:
44
pull_request:
55

6+
permissions:
7+
contents: read
8+
pull-requests: read
9+
610
jobs:
711
static-analysis:
8-
uses: mindee/mindee-api-php/.github/workflows/_static-analysis.yml@main
12+
uses: ./.github/workflows/_static-analysis.yml@main
913
static-dependency-checks:
10-
uses: mindee/mindee-api-php/.github/workflows/_static-dependency-checks.yml@main
14+
uses: ./.github/workflows/_static-dependency-checks.yml@main
1115
needs: static-analysis
1216
test-units:
13-
uses: mindee/mindee-api-php/.github/workflows/_test-units.yml@main
17+
uses: ./.github/workflows/_test-units.yml@main
1418
needs: static-analysis
1519
secrets: inherit
1620
test-integrations:
17-
uses: mindee/mindee-api-php/.github/workflows/_test-integrations.yml@main
21+
uses: ./.github/workflows/_test-integrations.yml@main
1822
needs: test-units
1923
secrets: inherit
2024
test-code-samples:
21-
uses: mindee/mindee-api-php/.github/workflows/_test-code-samples.yml@main
25+
uses: ./.github/workflows/_test-code-samples.yml@main
2226
needs: test-units
2327
secrets: inherit
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Mindee\Error;
4+
5+
/**
6+
* Exceptions relating to HTTP errors for the V2 API.
7+
*/
8+
class MindeeV2HttpException extends MindeeException
9+
{
10+
/**
11+
* @var integer Status code as sent by the server.
12+
*/
13+
public int $status;
14+
/**
15+
* @var string|null Details on the exception.
16+
*/
17+
public ?string $detail;
18+
19+
/**
20+
* @param integer $status HTTP status code, defaults to -1 if not set.
21+
* @param string|null $detail Optional details on the exception.
22+
*/
23+
public function __construct(int $status, string $detail = null)
24+
{
25+
parent::__construct("HTTP Error $status - $detail");
26+
$this->status = $status;
27+
$this->detail = $detail;
28+
}
29+
}

src/Http/MindeeApiV2.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
include_once(dirname(__DIR__) . '/version.php');
1515
// phpcs:enable
1616

17+
use Mindee\Error\MindeeV2HttpException;
1718
use Mindee\Input\InferenceParameters;
1819
use Mindee\Input\InputSource;
1920
use Mindee\Input\LocalInputSource;
2021
use Mindee\Input\URLInputSource;
21-
use Mindee\Parsing\Common\AsyncPredictResponse;
2222
use Mindee\Parsing\V2\InferenceResponse;
2323
use Mindee\Parsing\V2\JobResponse;
2424

@@ -163,6 +163,7 @@ public function reqPostInferenceEnqueue(InputSource $inputDoc, InferenceParamete
163163
* @param string $responseType Class name of the response type to instantiate.
164164
* @return JobResponse|InferenceResponse The processed response object.
165165
* @throws MindeeException Throws if HTTP status indicates an error or deserialization fails.
166+
* @throws MindeeV2HttpException Throws if the HTTP status indicates an error.
166167
*/
167168
private function processResponse(array $result, string $responseType)
168169
{
@@ -172,16 +173,14 @@ private function processResponse(array $result, string $responseType)
172173
$responseData = json_decode($result['data'], true);
173174

174175
if ($responseData && isset($responseData['status'])) {
175-
throw new MindeeException(
176-
"HTTP {$responseData['status']}: " . ($responseData['detail'] ?? 'Unknown error.'),
177-
ErrorCode::API_REQUEST_FAILED
176+
throw new MindeeV2HttpException(
177+
$responseData['status'],
178+
$responseData['detail'] ?? 'Unknown error.'
178179
);
179180
}
180181

181-
throw new MindeeException(
182-
"HTTP {$statusCode}: " . ($result['data'] ?? 'Unknown error.'),
183-
ErrorCode::API_REQUEST_FAILED
184-
);
182+
$detail = $responseData && $responseData['detail'] ? $responseData['detail'] : 'Unknown Error';
183+
throw new MindeeException($result['code'] ?? -1, $detail);
185184
}
186185

187186
try {

src/Input/LocalInputSource.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,4 +398,23 @@ public function hasSourceText(): bool
398398
}
399399
return PDFUtils::hasSourceText($this->filePath);
400400
}
401+
402+
403+
/**
404+
* Applies PDF-specific operations on the current file based on the specified PageOptions.
405+
*
406+
* @param PageOptions|null $pageOptions The options specifying which pages to modify or retain in the PDF file.
407+
* @return void
408+
* @throws MindeePDFException If a PDF processing error occurs during the operation.
409+
*/
410+
public function applyPageOptions(?PageOptions $pageOptions): void
411+
{
412+
if ($pageOptions !== null && $this->isPDF()) {
413+
$this->processPDF(
414+
$pageOptions->operation,
415+
$pageOptions->onMinPage,
416+
$pageOptions->pageIndexes
417+
);
418+
}
419+
}
401420
}

src/Input/LocalResponse.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace Mindee\Input;
44

5+
use Exception;
56
use Mindee\Error\ErrorCode;
67
use Mindee\Error\MindeeException;
8+
use Mindee\Parsing\V2\InferenceResponse;
79

810
/**
911
* Local response loaded from a file.
@@ -114,4 +116,26 @@ public function isValidHMACSignature(string $secretKey, string $signature): bool
114116
{
115117
return $signature === $this->getHMACSignature($secretKey);
116118
}
119+
/**
120+
* Deserialize the loaded local response into the requested CommonResponse-derived class.
121+
*
122+
* Typically used when dealing with V2 webhook callbacks.
123+
*
124+
* @param string $responseClass The class name into which the payload should be deserialized.
125+
* @return mixed An instance of responseClass populated with the file content.
126+
* @throws MindeeException If the provided class cannot be instantiated.
127+
*/
128+
public function deserializeResponse(string $responseClass)
129+
{
130+
try {
131+
$data = $this->toArray();
132+
return new $responseClass($data);
133+
} catch (Exception $e) {
134+
throw new MindeeException(
135+
"Invalid class specified for deserialization: " . $e->getMessage(),
136+
ErrorCode::INTERNAL_LIBRARY_ERROR,
137+
$e
138+
);
139+
}
140+
}
117141
}

src/Parsing/V2/Field/InferenceFields.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
namespace Mindee\Parsing\V2\Field;
44

55
use ArrayIterator;
6-
use IteratorAggregate;
6+
use ArrayObject;
77

88
/**
99
* Collection of inference fields.
1010
*/
11-
class InferenceFields implements IteratorAggregate
11+
class InferenceFields extends ArrayObject
1212
{
1313
/**
1414
* @var array<string, SimpleField|ObjectField|ListField>
@@ -31,6 +31,7 @@ public function __construct(array $serverResponse, int $indentLevel = 0)
3131
foreach ($serverResponse as $key => $value) {
3232
$this->fields[$key] = BaseField::createField($value, 1);
3333
}
34+
parent::__construct($this->fields);
3435
}
3536

3637
/**
@@ -88,8 +89,8 @@ public function toString(?int $indent = 0): string
8889
} elseif ($fieldValue instanceof ObjectField) {
8990
$line .= $fieldValue->__toString();
9091
} elseif ($fieldValue instanceof SimpleField) {
91-
$value = $fieldValue->value;
92-
if ($value !== null) {
92+
$value = $fieldValue->__toString();
93+
if ($value != '') {
9394
$line .= ' ' . $value;
9495
}
9596
}

src/Parsing/V2/Field/SimpleField.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ public function __construct(array $serverResponse, int $indentLevel = 0)
2727
*/
2828
public function __toString(): string
2929
{
30-
return $this->value !== null ? (string) $this->value : '';
30+
if (is_bool($this->value)) {
31+
return $this->value ? 'True' : 'False';
32+
}
33+
if (is_numeric($this->value)) {
34+
$value = (float)$this->value;
35+
return $value == (int)$value ?
36+
number_format($value, 1, '.', '') :
37+
rtrim(rtrim(number_format($value, 5, '.', ''), '0'), '.');
38+
}
39+
40+
return $this->value !== null ? (string)$this->value : '';
3141
}
3242
}

tests/ClientV2Test.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
use Mindee\ClientV2;
4+
use Mindee\Http\MindeeApiV2;
5+
use Mindee\Input\InferenceParameters;
6+
use Mindee\Input\LocalInputSource;
7+
use Mindee\Input\LocalResponse;
8+
use Mindee\Input\PathInput;
9+
use Mindee\Parsing\V2\InferenceResponse;
10+
use Mindee\Parsing\V2\JobResponse;
11+
use PHPUnit\Framework\MockObject\MockObject;
12+
use PHPUnit\Framework\TestCase;
13+
14+
15+
class ClientV2Test extends TestCase
16+
{
17+
private static function makeClientWithMockedApi(MindeeApiV2 $mockedApi): ClientV2
18+
{
19+
$client = new ClientV2("dummy");
20+
$reflection = new \ReflectionClass($client);
21+
$property = $reflection->getProperty('mindeeApi');
22+
$property->setAccessible(true);
23+
$property->setValue($client, $mockedApi);
24+
return $client;
25+
}
26+
27+
public function testEnqueuePostAsync(): void
28+
{
29+
$predictable = $this->createMock(MindeeApiV2::class);
30+
$syntheticResponse = file_get_contents(__DIR__ . '/resources/v2/job/ok_processing.json');
31+
$predictable->expects($this->once())
32+
->method('reqPostInferenceEnqueue')
33+
->with(
34+
$this->isInstanceOf(LocalInputSource::class),
35+
$this->isInstanceOf(InferenceParameters::class)
36+
)
37+
->willReturn(new JobResponse(json_decode($syntheticResponse, true)));
38+
39+
$mindeeClient = self::makeClientWithMockedApi($predictable);
40+
41+
$input = new PathInput(__DIR__ . '/resources/file_types/pdf/blank_1.pdf');
42+
$params = new InferenceParameters('dummy-model-id');
43+
44+
$response = $mindeeClient->enqueueInference($input, $params);
45+
46+
$this->assertNotNull($response, 'enqueue() must return a response');
47+
$this->assertInstanceOf(JobResponse::class, $response);
48+
}
49+
50+
public function testDocumentGetJobAsync(): void
51+
{
52+
/** @var MindeeApiV2&MockObject $predictable */
53+
$predictable = $this->createMock(MindeeApiV2::class);
54+
55+
$syntheticResponse = file_get_contents(__DIR__ . '/resources/v2/job/ok_processing.json');
56+
$processing = new JobResponse(json_decode($syntheticResponse, true));
57+
58+
$predictable->expects($this->once())
59+
->method('reqGetJob')
60+
->with($this->equalTo('dummy-id'))
61+
->willReturn($processing);
62+
63+
$mindeeClient = self::makeClientWithMockedApi($predictable);
64+
65+
$response = $mindeeClient->getJob('dummy-id');
66+
67+
$this->assertNotNull($response, 'must return a response');
68+
$this->assertNotNull($response->job, 'job must return a response');
69+
}
70+
71+
public function testDocumentGetInferenceAsync(): void
72+
{
73+
/** @var MindeeApiV2&MockObject $predictable */
74+
$predictable = $this->createMock(MindeeApiV2::class);
75+
76+
$jsonFile = __DIR__ . '/resources/v2/products/financial_document/complete.json';
77+
$this->assertFileExists($jsonFile, 'Test resource file must exist');
78+
79+
$json = json_decode(file_get_contents($jsonFile), true);
80+
$processing = new InferenceResponse($json);
81+
82+
$predictable->expects($this->once())
83+
->method('reqGetInference')
84+
->with($this->equalTo('12345678-1234-1234-1234-123456789abc'))
85+
->willReturn($processing);
86+
87+
$mindeeClient = self::makeClientWithMockedApi($predictable);
88+
89+
$response = $mindeeClient->getInference('12345678-1234-1234-1234-123456789abc');
90+
91+
$this->assertNotNull($response, 'must have a response');
92+
$this->assertNotNull($response->inference, 'inference must have a response');
93+
94+
$fields = $response->inference->result->fields ?? [];
95+
$this->assertCount(
96+
21,
97+
$fields,
98+
'Result must have 21 fields'
99+
);
100+
101+
$supplierName = $fields['supplier_name']->value ?? null;
102+
$this->assertEquals(
103+
'John Smith',
104+
$supplierName,
105+
'Result "' . $supplierName . '" must deserialize fields properly.'
106+
);
107+
}
108+
109+
public function testInferenceLoadsLocally(): void
110+
{
111+
$jsonFile = __DIR__ . '/resources/v2/products/financial_document/complete.json';
112+
$this->assertFileExists($jsonFile, 'Test resource file must exist');
113+
114+
$localResponse = new LocalResponse($jsonFile);
115+
$loaded = $localResponse->deserializeResponse(InferenceResponse::class);
116+
117+
$this->assertNotNull($loaded, 'Loaded InferenceResponse must not be null');
118+
$this->assertInstanceOf(InferenceResponse::class, $loaded);
119+
120+
$modelId = $loaded->inference->model->id ?? null;
121+
$this->assertEquals(
122+
'12345678-1234-1234-1234-123456789abc',
123+
$modelId,
124+
'Model Id mismatch'
125+
);
126+
127+
$supplierName = $loaded->inference->result->fields['supplier_name']->value ?? null;
128+
$this->assertEquals(
129+
'John Smith',
130+
$supplierName,
131+
'Supplier name mismatch'
132+
);
133+
}
134+
}

0 commit comments

Comments
 (0)