Skip to content

Commit b000382

Browse files
committed
fix(jsm): cap pagination at the documented int32 maximum
Addresses review: the schema claimed the int32 range but only floored at 0, so values above 2147483647 were forwarded to Atlassian instead of being rejected at Sim's boundary. Also restores the const tuple for the paginated operation list and drops the widened ToolConfig from the test table.
1 parent ba3fd4c commit b000382

4 files changed

Lines changed: 65 additions & 83 deletions

File tree

apps/sim/blocks/blocks/jira_service_management.test.ts

Lines changed: 56 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -29,97 +29,81 @@ import {
2929
jsmGetSlaTool,
3030
jsmGetTransitionsTool,
3131
} from '@/tools/jsm'
32-
import type { ToolConfig } from '@/tools/types'
32+
import type { ToolConfig, ToolResponse } from '@/tools/types'
3333

3434
const DOMAIN = 'example.atlassian.net'
3535
/** Injected by the executor from the OAuth credential before the tool's `body` runs. */
3636
const ACCESS_TOKEN = 'token-123'
3737

3838
interface PaginatedCase {
3939
operation: string
40-
tool: ToolConfig<any, any>
40+
toolId: string
41+
/** The tool's own `request.body`, captured at its concrete param type by `paginatedCase`. */
42+
buildBody: (params: Record<string, unknown>) => Record<string, unknown>
4143
schema: z.ZodType
4244
extraInputs: Record<string, string>
4345
}
4446

47+
/**
48+
* Captures each tool at its own generic so an incompatible tool/contract pairing is still a type
49+
* error at the call site, rather than being erased by a widened `ToolConfig` in the table type.
50+
*/
51+
function paginatedCase<P, R extends ToolResponse>(
52+
operation: string,
53+
tool: ToolConfig<P, R>,
54+
schema: z.ZodType,
55+
extraInputs: Record<string, string> = {}
56+
): PaginatedCase {
57+
return {
58+
operation,
59+
toolId: tool.id,
60+
buildBody: (params) => {
61+
const bodyFn = tool.request.body
62+
if (!bodyFn) throw new Error(`${tool.id} is missing request.body`)
63+
return bodyFn(params as P) as Record<string, unknown>
64+
},
65+
schema,
66+
extraInputs,
67+
}
68+
}
69+
4570
/**
4671
* Every paginated JSM operation, wired to the tool it resolves to and the contract its route
4772
* parses the body with. This walks the real chain — block `tools.config.params` → the tool's
4873
* `request.body` → the route contract — which is exactly where `jsm_get_comments` broke: the
4974
* tools declare `start`/`limit` as `type: 'number'` while the contract demanded strings.
5075
*/
5176
const PAGINATED_CASES: PaginatedCase[] = [
52-
{
53-
operation: 'get_service_desks',
54-
tool: jsmGetServiceDesksTool,
55-
schema: jsmServiceDesksBodySchema,
56-
extraInputs: {},
57-
},
58-
{
59-
operation: 'get_request_types',
60-
tool: jsmGetRequestTypesTool,
61-
schema: jsmRequestTypesToolBodySchema,
62-
extraInputs: { serviceDeskId: '1' },
63-
},
64-
{
65-
operation: 'get_requests',
66-
tool: jsmGetRequestsTool,
67-
schema: jsmRequestsBodySchema,
68-
extraInputs: {},
69-
},
70-
{
71-
operation: 'get_comments',
72-
tool: jsmGetCommentsTool,
73-
schema: jsmCommentsBodySchema,
74-
extraInputs: { issueIdOrKey: 'SD-123' },
75-
},
76-
{
77-
operation: 'get_customers',
78-
tool: jsmGetCustomersTool,
79-
schema: jsmCustomersBodySchema,
80-
extraInputs: { serviceDeskId: '1' },
81-
},
82-
{
83-
operation: 'get_organizations',
84-
tool: jsmGetOrganizationsTool,
85-
schema: jsmServiceDeskScopedBodySchema,
86-
extraInputs: { serviceDeskId: '1' },
87-
},
88-
{
89-
operation: 'get_queues',
90-
tool: jsmGetQueuesTool,
91-
schema: jsmQueuesBodySchema,
92-
extraInputs: { serviceDeskId: '1' },
93-
},
94-
{
95-
operation: 'get_sla',
96-
tool: jsmGetSlaTool,
97-
schema: jsmIssuePaginationBodySchema,
98-
extraInputs: { issueIdOrKey: 'SD-123' },
99-
},
100-
{
101-
operation: 'get_transitions',
102-
tool: jsmGetTransitionsTool,
103-
schema: jsmIssuePaginationBodySchema,
104-
extraInputs: { issueIdOrKey: 'SD-123' },
105-
},
106-
{
107-
operation: 'get_participants',
108-
tool: jsmGetParticipantsTool,
109-
schema: jsmParticipantsBodySchema,
110-
extraInputs: { issueIdOrKey: 'SD-123' },
111-
},
112-
{
113-
operation: 'get_approvals',
114-
tool: jsmGetApprovalsTool,
115-
schema: jsmApprovalsBodySchema,
116-
extraInputs: { issueIdOrKey: 'SD-123' },
117-
},
77+
paginatedCase('get_service_desks', jsmGetServiceDesksTool, jsmServiceDesksBodySchema),
78+
paginatedCase('get_request_types', jsmGetRequestTypesTool, jsmRequestTypesToolBodySchema, {
79+
serviceDeskId: '1',
80+
}),
81+
paginatedCase('get_requests', jsmGetRequestsTool, jsmRequestsBodySchema),
82+
paginatedCase('get_comments', jsmGetCommentsTool, jsmCommentsBodySchema, {
83+
issueIdOrKey: 'SD-123',
84+
}),
85+
paginatedCase('get_customers', jsmGetCustomersTool, jsmCustomersBodySchema, {
86+
serviceDeskId: '1',
87+
}),
88+
paginatedCase('get_organizations', jsmGetOrganizationsTool, jsmServiceDeskScopedBodySchema, {
89+
serviceDeskId: '1',
90+
}),
91+
paginatedCase('get_queues', jsmGetQueuesTool, jsmQueuesBodySchema, { serviceDeskId: '1' }),
92+
paginatedCase('get_sla', jsmGetSlaTool, jsmIssuePaginationBodySchema, { issueIdOrKey: 'SD-123' }),
93+
paginatedCase('get_transitions', jsmGetTransitionsTool, jsmIssuePaginationBodySchema, {
94+
issueIdOrKey: 'SD-123',
95+
}),
96+
paginatedCase('get_participants', jsmGetParticipantsTool, jsmParticipantsBodySchema, {
97+
issueIdOrKey: 'SD-123',
98+
}),
99+
paginatedCase('get_approvals', jsmGetApprovalsTool, jsmApprovalsBodySchema, {
100+
issueIdOrKey: 'SD-123',
101+
}),
118102
]
119103

120104
/** Run a set of block inputs through `tools.config.params`, then through the tool's request body. */
121105
function buildRequestBody(
122-
{ operation, tool, extraInputs }: PaginatedCase,
106+
{ operation, buildBody, extraInputs }: PaginatedCase,
123107
pagination: Record<string, string>
124108
) {
125109
const paramsFn = JiraServiceManagementBlock.tools.config?.params
@@ -133,22 +117,16 @@ function buildRequestBody(
133117
...pagination,
134118
})
135119

136-
const bodyFn = tool.request.body
137-
if (!bodyFn) throw new Error(`${tool.id} is missing request.body`)
138-
139-
return bodyFn({ ...toolParams, accessToken: ACCESS_TOKEN, domain: DOMAIN }) as Record<
140-
string,
141-
unknown
142-
>
120+
return buildBody({ ...toolParams, accessToken: ACCESS_TOKEN, domain: DOMAIN })
143121
}
144122

145123
describe.each(PAGINATED_CASES.map((testCase) => [testCase.operation, testCase] as const))(
146124
'JiraServiceManagementBlock %s',
147125
(_operation, testCase) => {
148126
it('resolves to the expected tool', () => {
149127
const toolFn = JiraServiceManagementBlock.tools.config?.tool
150-
expect(toolFn?.({ operation: testCase.operation })).toBe(testCase.tool.id)
151-
expect(JiraServiceManagementBlock.tools.access).toContain(testCase.tool.id)
128+
expect(toolFn?.({ operation: testCase.operation })).toBe(testCase.toolId)
129+
expect(JiraServiceManagementBlock.tools.access).toContain(testCase.toolId)
152130
})
153131

154132
it('sends a body its route contract accepts when pagination is filled in', () => {

apps/sim/blocks/blocks/jira_service_management.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { JsmResponse } from '@/tools/jsm/types'
66
import { getTrigger } from '@/triggers'
77

88
/** Operations that accept Atlassian's `start`/`limit` pagination query params. */
9-
const PAGINATED_OPERATIONS: string[] = [
9+
const PAGINATED_OPERATIONS = [
1010
'get_service_desks',
1111
'get_request_types',
1212
'get_requests',
@@ -18,7 +18,7 @@ const PAGINATED_OPERATIONS: string[] = [
1818
'get_transitions',
1919
'get_participants',
2020
'get_approvals',
21-
]
21+
] as const
2222

2323
/**
2424
* Coerce an optional numeric block input into an integer, returning undefined for
@@ -604,15 +604,15 @@ Return ONLY the comment text - no explanations.`,
604604
type: 'short-input',
605605
placeholder: 'Pagination start index (default: 0)',
606606
mode: 'advanced',
607-
condition: { field: 'operation', value: PAGINATED_OPERATIONS },
607+
condition: { field: 'operation', value: [...PAGINATED_OPERATIONS] },
608608
},
609609
{
610610
id: 'maxResults',
611611
title: 'Max Results',
612612
type: 'short-input',
613613
placeholder: 'Maximum results (default: 50)',
614614
mode: 'advanced',
615-
condition: { field: 'operation', value: PAGINATED_OPERATIONS },
615+
condition: { field: 'operation', value: [...PAGINATED_OPERATIONS] },
616616
},
617617
{
618618
id: 'assetSchemaId',

apps/sim/lib/api/contracts/selectors/jsm.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ describe('JSM contract pagination', () => {
6363
expect(() => jsmCommentsBodySchema.parse({ ...body, limit: 2.5 })).toThrow()
6464
expect(() => jsmCommentsBodySchema.parse({ ...body, start: -1 })).toThrow()
6565
expect(() => jsmCommentsBodySchema.parse({ ...body, limit: Number.NaN })).toThrow()
66+
expect(() => jsmCommentsBodySchema.parse({ ...body, limit: 2147483648 })).toThrow()
67+
expect(jsmCommentsBodySchema.parse({ ...body, limit: 2147483647 }).limit).toBe('2147483647')
68+
expect(jsmCommentsBodySchema.parse({ ...body, start: 0 }).start).toBe('0')
6669
})
6770

6871
/**

apps/sim/lib/api/contracts/selectors/jsm.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ const jsmPaginationField = z
4040
z
4141
.number()
4242
.int('Pagination values must be whole numbers')
43-
.min(0, 'Pagination values must be 0 or greater'),
43+
.min(0, 'Pagination values must be 0 or greater')
44+
.max(2147483647, 'Pagination values must be within the int32 range'),
4445
])
4546
.transform((value) => String(value))
4647
.optional()

0 commit comments

Comments
 (0)