| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 4064f91 commit f952aaa
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -158,9 +158,14 @@ describe('enrow_find_email', () => { | |||
| 158 | 158 | .mockResolvedValueOnce(jsonResponse(401, { message: 'invalid api key' })) | |
| 159 | 159 | vi.stubGlobal('fetch', fetchMock) | |
| 160 | 160 | ||
| 161 | - await expect( | ||
| 162 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 163 | - ).rejects.toThrow(/poll error: 401 - .*invalid api key/) | ||
| 161 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 162 | + submittedFindResult, | ||
| 163 | + findParams, | ||
| 164 | + executeTool | ||
| 165 | + ) | ||
| 166 | + | ||
| 167 | + expect(failed.success).toBe(false) | ||
| 168 | + expect(failed.error).toMatch(/poll error: 401 - .*invalid api key/) | ||
| 164 | 169 | ||
| 165 | 170 | expect(fetchMock).toHaveBeenCalledTimes(2) | |
| 166 | 171 | }) | |
@@ -250,9 +255,14 @@ describe('enrow_find_email', () => { | |||
| 250 | 255 | .mockRejectedValue(new DOMException('The operation timed out.', 'TimeoutError')) | |
| 251 | 256 | vi.stubGlobal('fetch', fetchMock) | |
| 252 | 257 | ||
| 253 | - await expect( | ||
| 254 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 255 | - ).rejects.toThrow('Enrow find-email did not complete within the polling window') | ||
| 258 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 259 | + submittedFindResult, | ||
| 260 | + findParams, | ||
| 261 | + executeTool | ||
| 262 | + ) | ||
| 263 | + | ||
| 264 | + expect(failed.success).toBe(false) | ||
| 265 | + expect(failed.error).toContain('Enrow find-email did not complete within the polling window') | ||
| 256 | 266 | ||
| 257 | 267 | expect(fetchMock).toHaveBeenCalledTimes(1) | |
| 258 | 268 | }) | |
@@ -261,9 +271,14 @@ describe('enrow_find_email', () => { | |||
| 261 | 271 | const fetchMock = vi.fn().mockRejectedValue(new TypeError('fetch failed')) | |
| 262 | 272 | vi.stubGlobal('fetch', fetchMock) | |
| 263 | 273 | ||
| 264 | - await expect( | ||
| 265 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 266 | - ).rejects.toThrow('fetch failed') | ||
| 274 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 275 | + submittedFindResult, | ||
| 276 | + findParams, | ||
| 277 | + executeTool | ||
| 278 | + ) | ||
| 279 | + | ||
| 280 | + expect(failed.success).toBe(false) | ||
| 281 | + expect(failed.error).toContain('fetch failed') | ||
| 267 | 282 | }) | |
| 268 | 283 | ||
| 269 | 284 | it('never sleeps past the wall-clock deadline when the polls themselves are slow', async () => { | |
@@ -286,9 +301,14 @@ describe('enrow_find_email', () => { | |||
| 286 | 301 | }) | |
| 287 | 302 | vi.stubGlobal('fetch', fetchMock) | |
| 288 | 303 | ||
| 289 | - await expect( | ||
| 290 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 291 | - ).rejects.toThrow('Enrow find-email did not complete within the polling window') | ||
| 304 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 305 | + submittedFindResult, | ||
| 306 | + findParams, | ||
| 307 | + executeTool | ||
| 308 | + ) | ||
| 309 | + | ||
| 310 | + expect(failed.success).toBe(false) | ||
| 311 | + expect(failed.error).toContain('Enrow find-email did not complete within the polling window') | ||
| 292 | 312 | ||
| 293 | 313 | // Second poll ends past the deadline, so its 15s backoff must be dropped to | |
| 294 | 314 | // zero rather than clamped against the 99s that `elapsed` still thinks it has. | |
@@ -308,9 +328,14 @@ describe('enrow_find_email', () => { | |||
| 308 | 328 | }) | |
| 309 | 329 | vi.stubGlobal('fetch', fetchMock) | |
| 310 | 330 | ||
| 311 | - await expect( | ||
| 312 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 313 | - ).rejects.toThrow('Enrow find-email did not complete within the polling window') | ||
| 331 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 332 | + submittedFindResult, | ||
| 333 | + findParams, | ||
| 334 | + executeTool | ||
| 335 | + ) | ||
| 336 | + | ||
| 337 | + expect(failed.success).toBe(false) | ||
| 338 | + expect(failed.error).toContain('Enrow find-email did not complete within the polling window') | ||
| 314 | 339 | ||
| 315 | 340 | expect(fetchMock).toHaveBeenCalledTimes(39) | |
| 316 | 341 | const total = sleepDelays().reduce((sum, delay) => sum + delay, 0) | |
@@ -322,9 +347,14 @@ describe('enrow_find_email', () => { | |||
| 322 | 347 | const fetchMock = vi.fn().mockResolvedValue(jsonResponse(500, { message: 'boom' })) | |
| 323 | 348 | vi.stubGlobal('fetch', fetchMock) | |
| 324 | 349 | ||
| 325 | - await expect( | ||
| 326 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 327 | - ).rejects.toThrow(/poll error: 500 - .*boom/) | ||
| 350 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 351 | + submittedFindResult, | ||
| 352 | + findParams, | ||
| 353 | + executeTool | ||
| 354 | + ) | ||
| 355 | + | ||
| 356 | + expect(failed.success).toBe(false) | ||
| 357 | + expect(failed.error).toMatch(/poll error: 500 - .*boom/) | ||
| 328 | 358 | ||
| 329 | 359 | expect(fetchMock).toHaveBeenCalledTimes(MAX_TRANSIENT_RETRIES + 1) | |
| 330 | 360 | }) | |
@@ -333,9 +363,14 @@ describe('enrow_find_email', () => { | |||
| 333 | 363 | const fetchMock = vi.fn().mockResolvedValue(jsonResponse(403, { message: 'forbidden' })) | |
| 334 | 364 | vi.stubGlobal('fetch', fetchMock) | |
| 335 | 365 | ||
| 336 | - await expect( | ||
| 337 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 338 | - ).rejects.toThrow(/poll error: 403 - .*forbidden/) | ||
| 366 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 367 | + submittedFindResult, | ||
| 368 | + findParams, | ||
| 369 | + executeTool | ||
| 370 | + ) | ||
| 371 | + | ||
| 372 | + expect(failed.success).toBe(false) | ||
| 373 | + expect(failed.error).toMatch(/poll error: 403 - .*forbidden/) | ||
| 339 | 374 | ||
| 340 | 375 | expect(fetchMock).toHaveBeenCalledTimes(1) | |
| 341 | 376 | }) | |
@@ -344,9 +379,14 @@ describe('enrow_find_email', () => { | |||
| 344 | 379 | const fetchMock = vi.fn().mockResolvedValue(jsonResponse(600, { message: 'nonsense' })) | |
| 345 | 380 | vi.stubGlobal('fetch', fetchMock) | |
| 346 | 381 | ||
| 347 | - await expect( | ||
| 348 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 349 | - ).rejects.toThrow(/poll error: 600 - .*nonsense/) | ||
| 382 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 383 | + submittedFindResult, | ||
| 384 | + findParams, | ||
| 385 | + executeTool | ||
| 386 | + ) | ||
| 387 | + | ||
| 388 | + expect(failed.success).toBe(false) | ||
| 389 | + expect(failed.error).toMatch(/poll error: 600 - .*nonsense/) | ||
| 350 | 390 | ||
| 351 | 391 | expect(fetchMock).toHaveBeenCalledTimes(1) | |
| 352 | 392 | }) | |
@@ -360,9 +400,14 @@ describe('enrow_find_email', () => { | |||
| 360 | 400 | const fetchMock = vi.fn().mockResolvedValue(stalled) | |
| 361 | 401 | vi.stubGlobal('fetch', fetchMock) | |
| 362 | 402 | ||
| 363 | - await expect( | ||
| 364 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 365 | - ).rejects.toThrow('Enrow find-email did not complete within the polling window') | ||
| 403 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 404 | + submittedFindResult, | ||
| 405 | + findParams, | ||
| 406 | + executeTool | ||
| 407 | + ) | ||
| 408 | + | ||
| 409 | + expect(failed.success).toBe(false) | ||
| 410 | + expect(failed.error).toContain('Enrow find-email did not complete within the polling window') | ||
| 366 | 411 | }) | |
| 367 | 412 | ||
| 368 | 413 | it('keeps the status when an error body cannot be read', async () => { | |
@@ -373,18 +418,51 @@ describe('enrow_find_email', () => { | |||
| 373 | 418 | const fetchMock = vi.fn().mockResolvedValue(unreadable) | |
| 374 | 419 | vi.stubGlobal('fetch', fetchMock) | |
| 375 | 420 | ||
| 376 | - await expect( | ||
| 377 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 378 | - ).rejects.toThrow('Enrow find-email poll error: 403 - <unreadable body>') | ||
| 421 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 422 | + submittedFindResult, | ||
| 423 | + findParams, | ||
| 424 | + executeTool | ||
| 425 | + ) | ||
| 426 | + | ||
| 427 | + expect(failed.success).toBe(false) | ||
| 428 | + expect(failed.error).toContain('Enrow find-email poll error: 403 - <unreadable body>') | ||
| 429 | + }) | ||
| 430 | + | ||
| 431 | + it('reports a failed poll as a failed tool response, not a null-field success', async () => { | ||
| 432 | + const fetchMock = vi.fn().mockResolvedValue(jsonResponse(403, { message: 'forbidden' })) | ||
| 433 | + vi.stubGlobal('fetch', fetchMock) | ||
| 434 | + | ||
| 435 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 436 | + submittedFindResult, | ||
| 437 | + findParams, | ||
| 438 | + executeTool | ||
| 439 | + ) | ||
| 440 | + | ||
| 441 | + /* | ||
| 442 | + * `executeTool` swallows a throw from `postProcess` and restores the submit | ||
| 443 | + * response — `success: true` with every field null — so the failure has to | ||
| 444 | + * be returned rather than thrown or the user sees a successful lookup that | ||
| 445 | + * merely found nothing. | ||
| 446 | + */ | ||
| 447 | + expect(failed.success).toBe(false) | ||
| 448 | + expect(failed.error).toMatch(/poll error: 403/) | ||
| 449 | + expect(failed.output.id).toBe(submittedFindResult.output.id) | ||
| 450 | + expect(failed.output.email).toBeNull() | ||
| 451 | + expect(failed.output.qualification).toBeNull() | ||
| 379 | 452 | }) | |
| 380 | 453 | ||
| 381 | 454 | it('gives up after the polling window when every poll stays 202', async () => { | |
| 382 | 455 | const fetchMock = vi.fn().mockResolvedValue(jsonResponse(202, { qualification: 'ongoing' })) | |
| 383 | 456 | vi.stubGlobal('fetch', fetchMock) | |
| 384 | 457 | ||
| 385 | - await expect( | ||
| 386 | - enrowFindEmailTool.postProcess!(submittedFindResult, findParams, executeTool) | ||
| 387 | - ).rejects.toThrow('Enrow find-email did not complete within the polling window') | ||
| 458 | + const failed = await enrowFindEmailTool.postProcess!( | ||
| 459 | + submittedFindResult, | ||
| 460 | + findParams, | ||
| 461 | + executeTool | ||
| 462 | + ) | ||
| 463 | + | ||
| 464 | + expect(failed.success).toBe(false) | ||
| 465 | + expect(failed.error).toContain('Enrow find-email did not complete within the polling window') | ||
| 388 | 466 | ||
| 389 | 467 | expect(fetchMock).toHaveBeenCalledTimes(MAX_POLLS) | |
| 390 | 468 | }) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,3 +1,4 @@ | |||
| 1 | + import { getErrorMessage } from '@sim/utils/errors' | ||
| 1 | 2 | import { enrowHosting } from '@/tools/enrow/hosting' | |
| 2 | 3 | import { pollEnrowJob } from '@/tools/enrow/poll' | |
| 3 | 4 | import type { | |
@@ -134,12 +135,41 @@ export const enrowFindEmailTool: ToolConfig<EnrowFindEmailParams, EnrowFindEmail | |||
| 134 | 135 | throw new Error('Enrow find-email did not return a job id to poll') | |
| 135 | 136 | } | |
| 136 | 137 | ||
| 137 | - const data = await pollEnrowJob({ | ||
| 138 | - resultUrl: 'https://api.enrow.io/email/find/single', | ||
| 139 | - jobId, | ||
| 140 | - apiKey: params.apiKey, | ||
| 141 | - label: 'Enrow find-email', | ||
| 142 | - }) | ||
| 138 | + /* | ||
| 139 | + * A failed poll must surface as a failed tool response, not as a throw. | ||
| 140 | + * | ||
| 141 | + * `executeTool` wraps every `postProcess` call in a catch that logs and | ||
| 142 | + * then restores the pre-`postProcess` result. That result is the *submit* | ||
| 143 | + * response — `success: true` with every result field null — so an escaping | ||
| 144 | + * error would be reported to the user as a successful lookup that simply | ||
| 145 | + * found nothing. Returning the failure explicitly keeps the reason | ||
| 146 | + * attached, and `success: false` also stops the hosted-key cost hook from | ||
| 147 | + * billing an execution that produced no result. | ||
| 148 | + */ | ||
| 149 | + let data: Record<string, unknown> | ||
| 150 | + try { | ||
| 151 | + data = await pollEnrowJob({ | ||
| 152 | + resultUrl: 'https://api.enrow.io/email/find/single', | ||
| 153 | + jobId, | ||
| 154 | + apiKey: params.apiKey, | ||
| 155 | + label: 'Enrow find-email', | ||
| 156 | + }) | ||
| 157 | + } catch (error) { | ||
| 158 | + return { | ||
| 159 | + success: false, | ||
| 160 | + error: getErrorMessage(error, 'Enrow find-email polling failed'), | ||
| 161 | + output: { | ||
| 162 | + id: jobId, | ||
| 163 | + email: null, | ||
| 164 | + qualification: null, | ||
| 165 | + fullname: null, | ||
| 166 | + firstname: null, | ||
| 167 | + lastname: null, | ||
| 168 | + company_name: null, | ||
| 169 | + company_domain: null, | ||
| 170 | + }, | ||
| 171 | + } | ||
| 172 | + } | ||
| 143 | 173 | ||
| 144 | 174 | return { | |
| 145 | 175 | success: true, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -51,12 +51,14 @@ export function enrowHosting<P>( | |||
| 51 | 51 | * call, so the poll loop's own GETs are not counted against it; nothing | |
| 52 | 52 | * here throttles a single call's polling. | |
| 53 | 53 | * | |
| 54 | - * That is the right shape for what Enrow actually publishes. Its | ||
| 54 | + * It is still the right order of magnitude for what Enrow publishes. Its | ||
| 55 | 55 | * documented limit is 10 req/s per API key on every *POST* endpoint — | |
| 56 | - * 600/min (https://docs.enrow.io/rate-limits). Each execution issues | ||
| 57 | - * exactly one POST, so 60/min sits an order of magnitude under the | ||
| 58 | - * documented ceiling. The polling GETs are not covered by that limit at | ||
| 59 | - * all, and each one is separated by a 3s interval within a 120s budget. | ||
| 56 | + * 600/min (https://docs.enrow.io/rate-limits). An admitted execution | ||
| 57 | + * issues at least one POST and, when `executeTool` retries an upstream | ||
| 58 | + * 429/503 and re-acquires a key, up to a small handful; even at that | ||
| 59 | + * ceiling 60 admissions/min stays an order of magnitude under 600. The | ||
| 60 | + * polling GETs are not covered by the documented POST limit at all, and | ||
| 61 | + * within one call they are serialized 3s apart under a 120s budget. | ||
| 60 | 62 | */ | |
| 61 | 63 | requestsPerMinute: 60, | |
| 62 | 64 | }, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,3 +1,4 @@ | |||
| 1 | + import { getErrorMessage } from '@sim/utils/errors' | ||
| 1 | 2 | import { enrowHosting } from '@/tools/enrow/hosting' | |
| 2 | 3 | import { pollEnrowJob } from '@/tools/enrow/poll' | |
| 3 | 4 | import type { | |
@@ -104,12 +105,36 @@ export const enrowVerifyEmailTool: ToolConfig<EnrowVerifyEmailParams, EnrowVerif | |||
| 104 | 105 | throw new Error('Enrow verify-email did not return a job id to poll') | |
| 105 | 106 | } | |
| 106 | 107 | ||
| 107 | - const data = await pollEnrowJob({ | ||
| 108 | - resultUrl: 'https://api.enrow.io/email/verify/single', | ||
| 109 | - jobId, | ||
| 110 | - apiKey: params.apiKey, | ||
| 111 | - label: 'Enrow verify-email', | ||
| 112 | - }) | ||
| 108 | + /* | ||
| 109 | + * A failed poll must surface as a failed tool response, not as a throw. | ||
| 110 | + * | ||
| 111 | + * `executeTool` wraps every `postProcess` call in a catch that logs and | ||
| 112 | + * then restores the pre-`postProcess` result. That result is the *submit* | ||
| 113 | + * response — `success: true` with every result field null — so an escaping | ||
| 114 | + * error would be reported to the user as a successful lookup that simply | ||
| 115 | + * found nothing. Returning the failure explicitly keeps the reason | ||
| 116 | + * attached, and `success: false` also stops the hosted-key cost hook from | ||
| 117 | + * billing an execution that produced no result. | ||
| 118 | + */ | ||
| 119 | + let data: Record<string, unknown> | ||
| 120 | + try { | ||
| 121 | + data = await pollEnrowJob({ | ||
| 122 | + resultUrl: 'https://api.enrow.io/email/verify/single', | ||
| 123 | + jobId, | ||
| 124 | + apiKey: params.apiKey, | ||
| 125 | + label: 'Enrow verify-email', | ||
| 126 | + }) | ||
| 127 | + } catch (error) { | ||
| 128 | + return { | ||
| 129 | + success: false, | ||
| 130 | + error: getErrorMessage(error, 'Enrow verify-email polling failed'), | ||
| 131 | + output: { | ||
| 132 | + id: jobId, | ||
| 133 | + email: null, | ||
| 134 | + qualification: null, | ||
| 135 | + }, | ||
| 136 | + } | ||
| 137 | + } | ||
| 113 | 138 | ||
| 114 | 139 | return { | |
| 115 | 140 | success: true, | |
| Back | FazBrowse Home | New Git URL |
0 commit comments