fix(jotform): harden the error envelope against quoted codes and non-JSON bodies

Jotform quotes `responseCode` on some endpoints and not others, so a
typeof-number test skipped the check on the quoted ones and turned an auth
failure into a successful tool result with empty output. Also caps the raw
body fallback, since an upstream gateway can answer with an HTML page
instead of the documented envelope.
This commit is contained in:
Waleed Latif
2026-08-16 22:54:44 -07:00
parent e56ec62859
commit 40e19236f1
2 changed files with 33 additions and 2 deletions
+24
View File
@@ -420,3 +420,27 @@ describe('label resources', () => {
expect(depth).toBeLessThanOrEqual(32)
})
})
describe('error envelope robustness', () => {
/**
* Jotform quotes `responseCode` on some endpoints and not others. A
* `typeof === 'number'` test silently skips the check on the quoted ones, turning
* an auth failure into a successful tool result with empty output.
*/
it('throws on a quoted non-2xx responseCode', async () => {
await expect(
parseJotformResponse(
jsonResponse({ responseCode: '401', message: 'Invalid API Key' }),
'Jotform Get Form'
)
).rejects.toThrow('Jotform Get Form error (401): Invalid API Key')
})
/** An upstream gateway can answer with an HTML page instead of the JSON envelope. */
it('caps a non-JSON error body instead of inlining the whole page', async () => {
const page = `<html>${'x'.repeat(5000)}</html>`
await expect(
parseJotformResponse(new Response(page, { status: 502 }), 'Jotform Get Form')
).rejects.toThrow(/^Jotform Get Form error \(502\): .{1,320}$/s)
})
})
+9 -2
View File
@@ -1,4 +1,5 @@
import { getErrorMessage } from '@sim/utils/errors'
import { truncate } from '@sim/utils/string'
/**
* Jotform serves the same REST surface from three regional hosts, and an API key
@@ -75,11 +76,17 @@ export async function parseJotformResponse(
}
const message = typeof data?.message === 'string' ? data.message : null
const responseCode = typeof data?.responseCode === 'number' ? data.responseCode : null
/* Jotform types `responseCode` inconsistently across endpoints, quoting it on some,
so a `typeof === 'number'` test would skip the check on the quoted ones. */
const responseCode = toNumberOrNull(data?.responseCode)
if (!response.ok || (responseCode !== null && (responseCode < 200 || responseCode >= 300))) {
const status = responseCode ?? response.status
throw new Error(`${label} error (${status}): ${message || text || response.statusText}`)
/* An error body is not always the documented JSON envelope — an upstream gateway
can return an HTML page — so the raw fallback is capped before it becomes the
error message. */
const detail = message || truncate(text, 300) || response.statusText
throw new Error(`${label} error (${status}): ${detail}`)
}
const rawResultSet = data?.resultSet as Record<string, unknown> | undefined