Skip to content

Commit 4710079

Browse files
committed
🐛 always use the provided result URL
1 parent 4f83106 commit 4710079

4 files changed

Lines changed: 84 additions & 32 deletions

File tree

src/http/apiCore.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { InputSource, PageOptions, LocalInputSource } from "@/input/index.js";
55
export const TIMEOUT_SECS_DEFAULT: number = 120;
66

77
export interface RequestOptions {
8-
hostname: string;
9-
path: string;
8+
hostname?: string;
9+
path?: string;
1010
method: any;
1111
timeoutSecs: number;
1212
headers: any;
@@ -33,15 +33,17 @@ export async function cutDocPages(inputDoc: InputSource, pageOptions: PageOption
3333
* Reads a response from the API and processes it.
3434
* @param dispatcher custom dispatcher to use for the request.
3535
* @param options options related to the request itself.
36+
* @param url override the URL of the request.
3637
* @returns the processed request.
3738
*/
3839
export async function sendRequestAndReadResponse(
3940
dispatcher: Dispatcher,
4041
options: RequestOptions,
42+
url?: string,
4143
): Promise<BaseHttpResponse> {
42-
const url: string = `https://${options.hostname}${options.path}`;
43-
44-
logger.debug(`${options.method}: ${url}`);
44+
if (!url) {
45+
url = `https://${options.hostname}${options.path}`;
46+
}
4547
const response = await request(
4648
url,
4749
{

src/v2/client.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export class Client {
9999
logger.debug(
100100
`Attempting to get inference with ID: ${inferenceId} using response type: ${product.name}`
101101
);
102-
return await this.mindeeApi.getProductResult(product, inferenceId);
102+
return await this.mindeeApi.getProductResultById(product, inferenceId);
103103
}
104104

105105
/**
@@ -155,7 +155,7 @@ export class Client {
155155
protected async pollForResult<P extends typeof BaseProduct>(
156156
product: typeof BaseProduct,
157157
pollingOptions: PollingOptions,
158-
queueId: string,
158+
jobId: string,
159159
): Promise<InstanceType<P["responseClass"]>> {
160160
logger.debug(
161161
`Waiting ${pollingOptions.initialDelaySec} seconds before polling.`
@@ -166,15 +166,15 @@ export class Client {
166166
pollingOptions.initialTimerOptions
167167
);
168168
logger.debug(
169-
`Start polling for inference using job ID: ${queueId}.`
169+
`Start polling for inference using job ID: ${jobId}.`
170170
);
171171
let retryCounter: number = 1;
172172
let pollResults: JobResponse;
173173
while (retryCounter < pollingOptions.maxRetries + 1) {
174174
logger.debug(
175175
`Attempt ${retryCounter} of ${pollingOptions.maxRetries}`
176176
);
177-
pollResults = await this.getJob(queueId);
177+
pollResults = await this.getJob(jobId);
178178
const error: ErrorResponse | undefined = pollResults.job.error;
179179
if (error) {
180180
throw new MindeeHttpErrorV2(error);
@@ -184,7 +184,12 @@ export class Client {
184184
break;
185185
}
186186
if (pollResults.job.status === "Processed") {
187-
return this.getResult(product, pollResults.job.id);
187+
if (pollResults.job.resultUrl === undefined) {
188+
throw new MindeeError(
189+
"The result URL is undefined. This is a server error, try again later or contact support."
190+
);
191+
}
192+
return this.mindeeApi.getProductResultByUrl(product, pollResults.job.resultUrl);
188193
}
189194
await setTimeout(
190195
pollingOptions.delaySec * 1000,

src/v2/http/mindeeApiV2.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
RequestOptions
1414
} from "@/http/apiCore.js";
1515
import { InputSource, LocalInputSource, UrlInput } from "@/input/index.js";
16-
import { MindeeDeserializationError } from "@/errors/index.js";
16+
import { MindeeDeserializationError, MindeeError } from "@/errors/index.js";
1717
import { MindeeHttpErrorV2 } from "./errors.js";
1818
import { logger } from "@/logger.js";
1919
import { BaseProduct } from "@/v2/product/baseProduct.js";
@@ -32,7 +32,7 @@ export class MindeeApiV2 {
3232
* @param inputSource Local file loaded as an input.
3333
* @param params {ExtractionParameters} parameters relating to the enqueueing options.
3434
* @category V2
35-
* @throws Error if the server's response contains one.
35+
* @throws Error if the server's response contains an error.
3636
* @returns a `Promise` containing a job response.
3737
*/
3838
async enqueueProduct(
@@ -52,7 +52,7 @@ export class MindeeApiV2 {
5252

5353
/**
5454
* Requests the results of a queued document from the API.
55-
* Throws an error if the server's response contains one.
55+
* Throws an error if the server's response contains an error.
5656
* @param jobId The document's ID in the queue.
5757
* @category Asynchronous
5858
* @returns a `Promise` containing information on the queue.
@@ -64,22 +64,38 @@ export class MindeeApiV2 {
6464

6565
/**
6666
* Requests the job of a queued document from the API.
67-
* Throws an error if the server's response contains one.
67+
* Throws an error if the server's response contains an error.
6868
* @param product
69-
* @param inferenceId The document's ID in the queue.
69+
* @param inferenceId The result's ID in the queue.
7070
* @category Asynchronous
7171
* @returns a `Promise` containing either the parsed result, or information on the queue.
7272
*/
73-
async getProductResult<P extends typeof BaseProduct>(
73+
async getProductResultById<P extends typeof BaseProduct>(
7474
product: P,
7575
inferenceId: string,
7676
): Promise<InstanceType<P["responseClass"]>> {
7777
const queueResponse: BaseHttpResponse = await this.#reqGetProductResult(
78-
inferenceId, product.slug
78+
`https://${this.settings.hostname}/v2/products/${product.slug}/results/${inferenceId}`
7979
);
8080
return this.#processResponse(queueResponse, product.responseClass) as InstanceType<P["responseClass"]>;
8181
}
8282

83+
/**
84+
* Requests the job of a queued document from the API.
85+
* Throws an error if the server's response contains an error.
86+
* @param product
87+
* @param url The document's ID in the queue.
88+
* @category Asynchronous
89+
* @returns a `Promise` containing either the parsed result, or information on the queue.
90+
*/
91+
async getProductResultByUrl<P extends typeof BaseProduct>(
92+
product: P,
93+
url: string,
94+
): Promise<InstanceType<P["responseClass"]>> {
95+
const queueResponse: BaseHttpResponse = await this.#reqGetProductResult(url);
96+
return this.#processResponse(queueResponse, product.responseClass) as InstanceType<P["responseClass"]>;
97+
}
98+
8399
#processResponse<T extends BaseResponse>(
84100
result: BaseHttpResponse,
85101
responseClass: ResponseConstructor<T>,
@@ -155,19 +171,19 @@ export class MindeeApiV2 {
155171

156172
/**
157173
* Make a request to GET the status of a document in the queue.
158-
* @param inferenceId ID of the inference.
159-
* @param slug "jobs" or "inferences"...
174+
* @param url URL path to the result.
160175
* @category Asynchronous
161-
* @returns a `Promise` containing either the parsed result, or information on the queue.
176+
* @returns a `Promise` containing the parsed result.
162177
*/
163-
async #reqGetProductResult(inferenceId: string, slug: string): Promise<BaseHttpResponse> {
178+
async #reqGetProductResult(url: string): Promise<BaseHttpResponse> {
164179
const options: RequestOptions = {
165180
method: "GET",
166181
headers: this.settings.baseHeaders,
167-
hostname: this.settings.hostname,
168-
path: `/v2/products/${slug}/results/${inferenceId}`,
169182
timeoutSecs: this.settings.timeoutSecs,
170183
};
171-
return await sendRequestAndReadResponse(this.settings.dispatcher, options);
184+
if (!url.startsWith("https://")) {
185+
throw new MindeeError("URL must start with https://");
186+
}
187+
return await sendRequestAndReadResponse(this.settings.dispatcher, options, url);
172188
}
173189
}

tests/v2/client/client.integration.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { before, beforeEach, describe, it } from "node:test";
12
import assert from "node:assert/strict";
23
import path from "node:path";
34

@@ -37,7 +38,7 @@ function checkEmptyActiveOptions(inference: ExtractionInference) {
3738
assert.equal(inference.activeOptions?.textContext, false);
3839
}
3940

40-
describe("MindeeV2 – Integration - Client", () => {
41+
describe("MindeeV2 – Integration - Client", { timeout: 120000 }, () => {
4142
let client: Client;
4243
let modelId: string;
4344

@@ -98,7 +99,7 @@ describe("MindeeV2 – Integration - Client", () => {
9899
assert.ok(inference.result);
99100
assert.equal(inference.result.rawText, undefined);
100101
checkEmptyActiveOptions(inference);
101-
}).timeout(60000);
102+
});
102103

103104
it("enqueueAndGetResult must succeed: Filled, single-page image – PathInput", async () => {
104105
const source = new PathInput({ inputPath: sampleImagePath });
@@ -137,7 +138,7 @@ describe("MindeeV2 – Integration - Client", () => {
137138
assert.equal(inference.activeOptions?.textContext, true);
138139

139140
assert.equal(inference.result.rawText?.pages.length, 1);
140-
}).timeout(120000);
141+
});
141142

142143
it("enqueueAndGetResult must succeed: Filled, single-page image – Base64Input", async () => {
143144
const data = fs.readFileSync(sampleBase64Path, "utf8");
@@ -168,7 +169,7 @@ describe("MindeeV2 – Integration - Client", () => {
168169
assert.equal(supplierField.value, "Clachan");
169170

170171
checkEmptyActiveOptions(inference);
171-
}).timeout(120000);
172+
});
172173

173174
it("enqueue must raise 422: Invalid model ID", async () => {
174175
const source = new PathInput({ inputPath: emptyPdfPath });
@@ -180,7 +181,7 @@ describe("MindeeV2 – Integration - Client", () => {
180181
} catch (err) {
181182
check422(err);
182183
}
183-
}).timeout(60000);
184+
});
184185

185186
it("getResult must raise 422: Invalid job ID", async () => {
186187
try {
@@ -192,7 +193,35 @@ describe("MindeeV2 – Integration - Client", () => {
192193
} catch (err) {
193194
check422(err);
194195
}
195-
}).timeout(60000);
196+
});
197+
198+
it("enqueue, getJob, and getResult must succeed", async () => {
199+
const source = new PathInput({ inputPath: emptyPdfPath });
200+
const params = {
201+
modelId,
202+
rag: false,
203+
rawText: false,
204+
polygon: false,
205+
confidence: false,
206+
alias: "ts_integration_all_together"
207+
};
208+
209+
const enqueueResponse = await client.enqueue(
210+
Extraction, source, params
211+
);
212+
assert.ok(enqueueResponse.job.id);
213+
214+
setTimeout(async () => {
215+
const jobResponse = await client.getJob(enqueueResponse.job.id);
216+
assert.ok(jobResponse.job.resultUrl);
217+
218+
const resultId = jobResponse.job.resultUrl?.split("/").pop() || "";
219+
const resultResponse = await client.getResult(
220+
Extraction, resultId
221+
);
222+
assert.strictEqual(resultId, resultResponse.inference.id);
223+
}, 6500);
224+
});
196225

197226
it("enqueueAndGetResult must succeed: HTTPS URL", async () => {
198227
const url = process.env.MINDEE_V2_SE_TESTS_BLANK_PDF_URL ?? "error-no-url-found";
@@ -211,7 +240,7 @@ describe("MindeeV2 – Integration - Client", () => {
211240
);
212241
assert.ok(response);
213242
assert.ok(response.inference instanceof ExtractionInference);
214-
}).timeout(60000);
243+
});
215244

216245
it("should override the data schema successfully", async () => {
217246
const source = new PathInput({ inputPath: emptyPdfPath });
@@ -233,6 +262,6 @@ describe("MindeeV2 – Integration - Client", () => {
233262
assert.ok(response.inference.result.fields.get("test_replace"));
234263
assert.equal((response.inference.result.fields.get("test_replace") as SimpleField).value, "a test value");
235264

236-
}).timeout(60000);
265+
});
237266

238267
});

0 commit comments

Comments
 (0)