Skip to content

Commit 87e353e

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(quickbooks): tighten document handling
1 parent 6278a01 commit 87e353e

3 files changed

Lines changed: 64 additions & 14 deletions

File tree

apps/sim/blocks/blocks/quickbooks.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,8 +2344,11 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
23442344
record: {
23452345
type: 'json',
23462346
description:
2347-
'Created, updated, or voided master-data, sales, purchasing, or accounting record with native QuickBooks fields',
2348-
condition: { field: 'operation', value: [...MUTATION_OPERATIONS] },
2347+
'Created, updated, voided, or emailed record with native QuickBooks fields when QuickBooks returns one',
2348+
condition: {
2349+
field: 'operation',
2350+
value: [...MUTATION_OPERATIONS, EMAIL_TRANSACTION_OPERATION],
2351+
},
23492352
},
23502353
recordId: {
23512354
type: 'string',

apps/sim/tools/quickbooks/documents.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,28 @@ describe('QuickBooks attachment metadata reads', () => {
188188
})
189189
})
190190

191+
it('surfaces sanitized faults from successful upload envelopes', async () => {
192+
const response = Response.json({
193+
AttachableResponse: [
194+
{
195+
Fault: {
196+
Error: [
197+
{
198+
Message: 'Invalid Uploaded File',
199+
Detail: 'The uploaded file is invalid',
200+
code: '6041',
201+
},
202+
],
203+
},
204+
},
205+
],
206+
})
207+
208+
await expect(parseQuickBooksAttachableResponse(response)).rejects.toThrow(
209+
'6041: Invalid Uploaded File: The uploaded file is invalid'
210+
)
211+
})
212+
191213
it('rejects unsupported targets and modes before fetch', () => {
192214
expect(() =>
193215
requestUrl({ ...auth, readMode: 'list', targetType: 'unknown' as never, targetId: '1' })
@@ -201,9 +223,22 @@ describe('QuickBooks document validation and block parity', () => {
201223
expect(sanitizeQuickBooksFileName('../unsafe/receipt?.pdf', 'fallback.pdf')).toBe(
202224
'receipt_.pdf'
203225
)
226+
expect(sanitizeQuickBooksFileName(undefined, '..')).toBe('quickbooks-file')
227+
expect(sanitizeQuickBooksFileName('\u0000', '../../safe-fallback.pdf')).toBe(
228+
'safe-fallback.pdf'
229+
)
204230
expect(validateQuickBooksAttachmentFileType('receipt.pdf', 'application/pdf')).toBe(
205231
'application/pdf'
206232
)
233+
expect(() => validateQuickBooksAttachmentFileType('scan.tiff', 'image/tiff')).toThrow(
234+
'does not support'
235+
)
236+
expect(() => validateQuickBooksAttachmentFileType('note.rtf', 'application/rtf')).toThrow(
237+
'does not support'
238+
)
239+
expect(() => validateQuickBooksAttachmentFileType('data.xml', 'application/xml')).toThrow(
240+
'does not support'
241+
)
207242
expect(() =>
208243
validateQuickBooksAttachmentFileType('script.exe', 'application/octet-stream')
209244
).toThrow('does not support')
@@ -224,6 +259,10 @@ describe('QuickBooks document validation and block parity', () => {
224259
expect(operation?.options?.map((option) => option.id).sort()).toEqual(
225260
[...(QuickBooksBlock.tools?.access ?? [])].sort()
226261
)
262+
expect(QuickBooksBlock.outputs?.record.condition).toEqual({
263+
field: 'operation',
264+
value: expect.arrayContaining(['quickbooks_email_transaction']),
265+
})
227266
const fileBlocks = QuickBooksBlock.subBlocks.filter(
228267
(block) => block.canonicalParamId === 'attachmentFile'
229268
)

apps/sim/tools/quickbooks/documents_utils.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,12 @@ const QUICKBOOKS_FILE_TYPES: Record<string, readonly string[]> = {
4747
ods: ['application/vnd.oasis.opendocument.spreadsheet'],
4848
pdf: ['application/pdf'],
4949
png: ['image/png'],
50-
rtf: ['application/rtf', 'text/rtf'],
50+
rtf: ['text/rtf'],
5151
tif: ['image/tiff'],
52-
tiff: ['image/tiff'],
5352
txt: ['text/plain'],
5453
xls: ['application/vnd.ms-excel'],
5554
xlsx: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
56-
xml: ['application/xml', 'text/xml'],
55+
xml: ['text/xml'],
5756
}
5857

5958
export function getQuickBooksDocumentTransaction(type: QuickBooksDocumentTransactionType) {
@@ -79,15 +78,17 @@ export function validateQuickBooksRecipient(recipient?: string): string | undefi
7978
}
8079

8180
export function sanitizeQuickBooksFileName(value: string | undefined, fallback: string): string {
82-
const raw = value?.trim() || fallback
83-
const leaf = raw.split(/[\\/]/).pop() || fallback
84-
const sanitized = leaf
85-
.replace(/[\u0000-\u001f\u007f]/g, '')
86-
.replace(/[^\w.() -]/g, '_')
87-
.trim()
88-
const bounded = sanitized.slice(0, 180)
89-
if (!bounded || bounded === '.' || bounded === '..') return fallback
90-
return bounded
81+
const sanitize = (candidate: string): string | undefined => {
82+
const leaf = candidate.trim().split(/[\\/]/).pop() ?? ''
83+
const bounded = leaf
84+
.replace(/[\u0000-\u001f\u007f]/g, '')
85+
.replace(/[^\w.() -]/g, '_')
86+
.trim()
87+
.slice(0, 180)
88+
return bounded && bounded !== '.' && bounded !== '..' ? bounded : undefined
89+
}
90+
91+
return (value ? sanitize(value) : undefined) ?? sanitize(fallback) ?? 'quickbooks-file'
9192
}
9293

9394
export function validateQuickBooksAttachmentFileType(fileName: string, mimeType: string): string {
@@ -123,6 +124,13 @@ export async function parseQuickBooksAttachableResponse(
123124
response,
124125
'QuickBooks Attachable response'
125126
)
127+
const nestedFault = data.AttachableResponse?.find((entry) => entry.Fault)?.Fault
128+
const sanitizedFault = sanitizeQuickBooksFaultData({ Fault: nestedFault })
129+
if (sanitizedFault) {
130+
throw new Error(
131+
`QuickBooks attachment upload failed: ${formatQuickBooksFaultDetail(sanitizedFault)}`
132+
)
133+
}
126134
const attachment = data.Attachable ?? data.AttachableResponse?.[0]?.Attachable
127135
if (!attachment || typeof attachment !== 'object' || Array.isArray(attachment)) {
128136
throw new Error('QuickBooks Attachable response is missing a valid attachment')

0 commit comments

Comments
 (0)