Skip to content

Commit bfe11ab

Browse files
committed
test(zoho-desk): cover the webhook subscription filter rules
The subscription filter logic had no test coverage at all, and it is where the last two rounds both found bugs - includePrevState on an event Zoho does not document it for, and departmentIds sent to events that accept no filters. Adds six cases against the real createSubscription: includePrevState is set for each of the five documented update events and NOT for Ticket_Comment_Update, departmentIds is kept for a filterable event and dropped for one that is not, and an event with no filters serializes as null rather than an empty object. Verified the guard bites: reverting PREV_STATE_EVENTS to the `endsWith('_Update')` rule turns the Ticket_Comment_Update case red. The Ticket_Comment_Update assertion checks the with-departments case as well as the bare one - asserting only `not.toHaveProperty` on the bare filter would pass vacuously, since that filter is legitimately null.
1 parent 39085bd commit bfe11ab

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

apps/sim/lib/webhooks/providers/zoho-desk.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,97 @@ describe('zohoDeskHandler', () => {
280280
expect(result?.providerConfigUpdates).toMatchObject({ externalId: 'wh-123' })
281281
expect(result?.providerConfigUpdates).not.toHaveProperty('ignoreSourceId')
282282
})
283+
284+
/** Drives createSubscription and returns the JSON body sent to Zoho. */
285+
async function captureSentBody(
286+
providerConfig: Record<string, unknown>
287+
): Promise<Record<string, unknown>> {
288+
vi.mocked(getCredentialOwner).mockResolvedValue({
289+
accountId: 'acc-1',
290+
userId: 'user-1',
291+
// biome-ignore lint/suspicious/noExplicitAny: partial owner shape for the test
292+
} as any)
293+
vi.mocked(refreshAccessTokenIfNeeded).mockResolvedValue('zoho-token')
294+
295+
let sentBody: Record<string, unknown> = {}
296+
vi.spyOn(globalThis, 'fetch').mockImplementation(async (_url, init) => {
297+
sentBody = JSON.parse(String((init as RequestInit).body))
298+
return {
299+
ok: true,
300+
status: 200,
301+
text: async () => JSON.stringify({ id: 'wh-123' }),
302+
} as unknown as Response
303+
})
304+
305+
await zohoDeskHandler.createSubscription?.({
306+
webhook: {
307+
id: 'w1',
308+
path: 'p1',
309+
providerConfig: { credentialId: 'cred-1', orgId: '700123', ...providerConfig },
310+
},
311+
workflow: {},
312+
userId: 'user-1',
313+
requestId: 'test',
314+
// biome-ignore lint/suspicious/noExplicitAny: request is unused on this path
315+
request: {} as any,
316+
})
317+
return sentBody
318+
}
319+
320+
// Zoho documents includePrevState on the Ticket/Contact/Agent/Task/Article
321+
// update events but NOT on Ticket_Comment_Update, which lists only
322+
// departmentIds. An `endsWith('_Update')` rule sent an undocumented filter
323+
// key on that one event.
324+
it.each(['Ticket_Update', 'Contact_Update', 'Agent_Update', 'Task_Update', 'Article_Update'])(
325+
'sets includePrevState for %s',
326+
async (eventType) => {
327+
const body = await captureSentBody({ eventType })
328+
expect((body.subscriptions as Record<string, unknown>)[eventType]).toMatchObject({
329+
includePrevState: true,
330+
})
331+
}
332+
)
333+
334+
it('does NOT set includePrevState for Ticket_Comment_Update', async () => {
335+
// With a department filter the object exists, so this proves the key is
336+
// absent rather than the whole filter being null for an unrelated reason.
337+
const withDepts = await captureSentBody({
338+
eventType: 'Ticket_Comment_Update',
339+
triggerDepartmentIds: '111',
340+
})
341+
expect((withDepts.subscriptions as Record<string, unknown>).Ticket_Comment_Update).toEqual({
342+
departmentIds: ['111'],
343+
})
344+
345+
// And with no filters at all it collapses to null, not `{includePrevState:true}`.
346+
const bare = await captureSentBody({ eventType: 'Ticket_Comment_Update' })
347+
expect((bare.subscriptions as Record<string, unknown>).Ticket_Comment_Update).toBeNull()
348+
})
349+
350+
// Zoho: events outside the ticket/task family "do not support filters.
351+
// Therefore, pass the value as null in the API request."
352+
it('drops departmentIds for an event whose filter does not accept it', async () => {
353+
const body = await captureSentBody({
354+
eventType: 'Contact_Add',
355+
triggerDepartmentIds: '111,222',
356+
})
357+
expect((body.subscriptions as Record<string, unknown>).Contact_Add).toBeNull()
358+
})
359+
360+
it('keeps departmentIds for an event whose filter accepts it', async () => {
361+
const body = await captureSentBody({
362+
eventType: 'Ticket_Add',
363+
triggerDepartmentIds: '111,222',
364+
})
365+
expect((body.subscriptions as Record<string, unknown>).Ticket_Add).toEqual({
366+
departmentIds: ['111', '222'],
367+
})
368+
})
369+
370+
it('sends null, not an empty object, when an event has no filters', async () => {
371+
const body = await captureSentBody({ eventType: 'Ticket_Delete' })
372+
expect((body.subscriptions as Record<string, unknown>).Ticket_Delete).toBeNull()
373+
})
283374
})
284375

285376
describe('mapZohoWebhookError', () => {

0 commit comments

Comments
 (0)