Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions apps/sim/tools/icypeas-hosting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,73 @@ describe('Icypeas verify-email pricing', () => {
})
})

describe('Icypeas transformResponse validation errors', () => {
it('verify-email throws the human-readable reason on a 200 validationErrors body', async () => {
const response = new Response(
JSON.stringify({
success: false,
validationErrors: [
{
field: 'type',
message: 'insufficient_credits',
humanReadableMessage: 'Insufficient credits to run the search',
type: 'InsufficientCredits',
},
],
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)

await expect(icypeasVerifyEmailTool.transformResponse!(response)).rejects.toThrow(
/Insufficient credits to run the search/
)
})

it('find-email throws the human-readable reason on a 200 validationErrors body', async () => {
const response = new Response(
JSON.stringify({
success: false,
validationErrors: [
{
field: 'type',
message: 'insufficient_credits',
humanReadableMessage: 'Insufficient credits to run the search',
type: 'InsufficientCredits',
},
],
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)

await expect(icypeasFindEmailTool.transformResponse!(response)).rejects.toThrow(
/Insufficient credits to run the search/
)
})

it('verify-email falls back to message when humanReadableMessage is absent', async () => {
const response = new Response(
JSON.stringify({
success: false,
validationErrors: [{ field: 'email', message: 'validation_required' }],
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)

await expect(icypeasVerifyEmailTool.transformResponse!(response)).rejects.toThrow(
/validation_required/
)
})

it('verify-email still throws when a success body has no item _id', async () => {
const response = new Response(JSON.stringify({ success: true, item: {} }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})

await expect(icypeasVerifyEmailTool.transformResponse!(response)).rejects.toThrow(/item _id/)
})
})

describe('Icypeas find-email postProcess poll', () => {
it('polls the results endpoint until terminal status and returns the email', async () => {
vi.useFakeTimers()
Expand Down
13 changes: 13 additions & 0 deletions apps/sim/tools/icypeas/find_email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ export const icypeasFindEmailTool: ToolConfig<IcypeasFindEmailParams, IcypeasFin
throw new Error(`Icypeas API error: ${response.status} - ${errorText}`)
}
const json = (await response.json()) as Record<string, unknown>
// Icypeas signals submit-time failures as HTTP 200 { success: false, validationErrors: [...] }
// — malformed input, insufficient credits, rate limits, etc. None of these are a
// search verdict (those only come from polling), so fail fast with the human-readable
// reason rather than a cryptic missing-_id error or a fabricated empty result.
const validationErrors = json.validationErrors
if (Array.isArray(validationErrors) && validationErrors.length > 0) {
const first = validationErrors[0] as Record<string, unknown> | undefined
const reason =
(first?.humanReadableMessage as string | undefined) ??
(first?.message as string | undefined) ??
'validation error'
throw new Error(`Icypeas email-search rejected the request: ${reason}`)
}
Comment thread
TheodoreSpeaks marked this conversation as resolved.
// Submit response: { success: true, item: { _id: '...', status: 'NONE', ... } }
const item = (json.item as Record<string, unknown> | undefined) ?? {}
const searchId = (item._id as string | undefined) ?? null
Expand Down
14 changes: 14 additions & 0 deletions apps/sim/tools/icypeas/verify_email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ export const icypeasVerifyEmailTool: ToolConfig<
throw new Error(`Icypeas API error: ${response.status} - ${errorText}`)
}
const json = (await response.json()) as Record<string, unknown>
// Icypeas signals submit-time failures as HTTP 200 { success: false, validationErrors: [...] }
// — malformed input, insufficient credits, rate limits, etc. None of these are a
// deliverability verdict (those only come from polling), so fail fast with the
// human-readable reason rather than a cryptic missing-_id error or a fabricated
// valid=false result.
const validationErrors = json.validationErrors
if (Array.isArray(validationErrors) && validationErrors.length > 0) {
const first = validationErrors[0] as Record<string, unknown> | undefined
const reason =
(first?.humanReadableMessage as string | undefined) ??
(first?.message as string | undefined) ??
'validation error'
throw new Error(`Icypeas email-verification rejected the request: ${reason}`)
}
Comment thread
TheodoreSpeaks marked this conversation as resolved.
// Submit response: { success: true, item: { _id: '...', status: 'NONE', ... } }
const item = (json.item as Record<string, unknown> | undefined) ?? {}
const searchId = (item._id as string | undefined) ?? null
Expand Down
Loading