Skip to content

Commit 41b172f

Browse files
waleedlatif1claude
andcommitted
azure_devops: address more bugbot comments
- Webhook provider extractIdempotencyId returns null when subscriptionId or notificationId is missing/empty, preventing the literal "azure_devops:undefined:undefined" key from collapsing unrelated deliveries into duplicates - Get Work Items Batch validates that at least one non-empty ID is supplied before issuing the API request, throwing a clear error instead of hitting an empty ids= query - Tests cover both behaviors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent cdc8041 commit 41b172f

3 files changed

Lines changed: 31 additions & 1 deletion

File tree

apps/sim/lib/webhooks/providers/azure-devops.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ export const azureDevOpsHandler: WebhookProviderHandler = {
4848
extractIdempotencyId(body: unknown): string | null {
4949
const obj = body as Record<string, unknown> | null
5050
if (!obj) return null
51-
return `azure_devops:${obj.subscriptionId}:${obj.notificationId}`
51+
const subscriptionId =
52+
typeof obj.subscriptionId === 'string' && obj.subscriptionId ? obj.subscriptionId : null
53+
const notificationId =
54+
typeof obj.notificationId === 'number' || typeof obj.notificationId === 'string'
55+
? String(obj.notificationId)
56+
: null
57+
if (!subscriptionId || !notificationId) return null
58+
return `azure_devops:${subscriptionId}:${notificationId}`
5259
},
5360

5461
async formatInput({ body, webhook, requestId }: FormatInputContext): Promise<FormatInputResult> {

apps/sim/tools/azure_devops/azure-devops.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,15 @@ describe('Azure DevOps response transforms', () => {
606606
expect(result.output.metadata.workItems).toHaveLength(2)
607607
})
608608

609+
it('throws when Get Work Items Batch is invoked with no valid IDs', () => {
610+
expect(() =>
611+
buildUrl(getWorkItemsBatchTool, {
612+
...baseParams,
613+
ids: ' , , ',
614+
} satisfies GetWorkItemsBatchParams)
615+
).toThrow(/requires at least one work item ID/)
616+
})
617+
609618
it('chunks Get Work Items Batch requests larger than 200 IDs', async () => {
610619
const fetchMock = vi
611620
.fn()
@@ -765,4 +774,15 @@ describe('Azure DevOps trigger event matching', () => {
765774
expect(isAzureDevOpsEventMatch('azure_devops_work_item_created', baseBuild)).toBe(false)
766775
expect(isAzureDevOpsEventMatch('azure_devops_webhook', { eventType: 'anything' })).toBe(true)
767776
})
777+
778+
it('extractIdempotencyId returns null when subscriptionId or notificationId is missing', async () => {
779+
const { azureDevOpsHandler } = await import('@/lib/webhooks/providers/azure-devops')
780+
expect(azureDevOpsHandler.extractIdempotencyId!({})).toBeNull()
781+
expect(azureDevOpsHandler.extractIdempotencyId!({ subscriptionId: 'sub-1' })).toBeNull()
782+
expect(azureDevOpsHandler.extractIdempotencyId!({ notificationId: 42 })).toBeNull()
783+
expect(
784+
azureDevOpsHandler.extractIdempotencyId!({ subscriptionId: 'sub-1', notificationId: 42 })
785+
).toBe('azure_devops:sub-1:42')
786+
expect(azureDevOpsHandler.extractIdempotencyId!(null)).toBeNull()
787+
})
768788
})

apps/sim/tools/azure_devops/get_work_items_batch.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ export const getWorkItemsBatchTool: ToolConfig<GetWorkItemsBatchParams, GetWorkI
4949
.split(',')
5050
.map((id) => id.trim())
5151
.filter(Boolean)
52+
if (allIds.length === 0) {
53+
throw new Error('Get Work Items Batch requires at least one work item ID.')
54+
}
5255
const firstChunk = allIds.slice(0, 200)
5356
const url = new URL(
5457
`https://dev.azure.com/${params.organization.trim()}/${params.project.trim()}/_apis/wit/workitems`

0 commit comments

Comments
 (0)