Skip to content

Commit 9bd1cb5

Browse files
SavioBS629claude
andcommitted
fix(testmanagement): apply case_type on createTestCase
createTestCase never accepted case_type as an input, so values passed by clients were silently dropped and the project default ("Other") was applied. Add case_type to the schema and normalize it alongside priority via a single form-fields lookup, matching updateTestCase behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e6e780f commit 9bd1cb5

2 files changed

Lines changed: 116 additions & 17 deletions

File tree

src/tools/testmanagement-utils/create-testcase.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface TestCaseCreateRequest {
4343
custom_fields?: Record<string, CustomFieldValue>;
4444
automation_status?: string;
4545
priority?: string;
46+
case_type?: string;
4647
template?: string;
4748
template_id?: number;
4849
}
@@ -158,6 +159,12 @@ export const CreateTestCaseSchema = z.object({
158159
.describe(
159160
"Priority of the test case. Accepts either display name (e.g. 'Critical', 'High', 'Medium', 'Low') or internal name (e.g. 'medium'). If omitted, the project default (usually 'Medium') is applied. Valid values are per-project and discoverable via the form-fields endpoint.",
160161
),
162+
case_type: z
163+
.string()
164+
.optional()
165+
.describe(
166+
"Test case type. Accepts either display name (e.g. 'Functional', 'Regression', 'Smoke & Sanity') or internal name (e.g. 'functional', 'smoke_sanity'). If omitted, the project default (usually 'Other') is applied. Valid values are per-project.",
167+
),
161168
template: z
162169
.string()
163170
.optional()
@@ -197,33 +204,45 @@ export function sanitizeArgs(args: any) {
197204
import { getBrowserStackAuth } from "../../lib/get-auth.js";
198205

199206
/**
200-
* Normalize priority to the display name the create endpoint accepts (it
201-
* rejects lowercase). On lookup failure, pass the raw value through.
207+
* Normalize priority and case_type to the display names the create endpoint
208+
* accepts (it rejects lowercase internal names). Fetches the project's
209+
* form-fields once; on lookup failure, passes raw values through.
202210
*/
203-
async function normalizePriority(
211+
async function normalizeDefaultFields(
204212
projectIdentifier: string,
205-
priority: string,
213+
fields: { priority?: string; case_type?: string },
206214
config: BrowserStackConfig,
207-
): Promise<string> {
215+
): Promise<{ priority?: string; case_type?: string }> {
208216
try {
209217
const numericProjectId = await projectIdentifierToId(
210218
projectIdentifier,
211219
config,
212220
);
213221
const { default_fields } = await fetchFormFields(numericProjectId, config);
214-
return (
215-
normalizeDefaultFieldValue(
216-
default_fields?.priority?.values ?? [],
217-
priority,
218-
"name",
219-
) ?? priority
220-
);
222+
const out: { priority?: string; case_type?: string } = {};
223+
if (fields.priority !== undefined) {
224+
out.priority =
225+
normalizeDefaultFieldValue(
226+
default_fields?.priority?.values ?? [],
227+
fields.priority,
228+
"name",
229+
) ?? fields.priority;
230+
}
231+
if (fields.case_type !== undefined) {
232+
out.case_type =
233+
normalizeDefaultFieldValue(
234+
default_fields?.case_type?.values ?? [],
235+
fields.case_type,
236+
"name",
237+
) ?? fields.case_type;
238+
}
239+
return out;
221240
} catch (err) {
222241
logger.warn(
223-
"Failed to normalize priority value; passing through as given: %s",
242+
"Failed to normalize default fields; passing through as given: %s",
224243
err instanceof Error ? err.message : String(err),
225244
);
226-
return priority;
245+
return { priority: fields.priority, case_type: fields.case_type };
227246
}
228247
}
229248

@@ -318,12 +337,22 @@ export async function createTestCase(
318337
): Promise<CallToolResult> {
319338
const testCaseParams: TestCaseCreateRequest = { ...params };
320339

321-
if (testCaseParams.priority !== undefined) {
322-
testCaseParams.priority = await normalizePriority(
340+
if (
341+
testCaseParams.priority !== undefined ||
342+
testCaseParams.case_type !== undefined
343+
) {
344+
const normalized = await normalizeDefaultFields(
323345
params.project_identifier,
324-
testCaseParams.priority,
346+
{
347+
priority: testCaseParams.priority,
348+
case_type: testCaseParams.case_type,
349+
},
325350
config,
326351
);
352+
if (normalized.priority !== undefined)
353+
testCaseParams.priority = normalized.priority;
354+
if (normalized.case_type !== undefined)
355+
testCaseParams.case_type = normalized.case_type;
327356
}
328357

329358
const authString = getBrowserStackAuth(config);

tests/tools/testmanagement.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,76 @@ describe('createTestCase — priority normalization', () => {
11621162
const body = (apiClientMock.post as Mock).mock.calls[0][0].body;
11631163
expect(body.test_case.priority).toBe('critical');
11641164
});
1165+
1166+
const caseTypeValues = [
1167+
{ name: 'Functional', internal_name: 'functional', value: 1 },
1168+
{ name: 'Regression', internal_name: 'regression', value: 2 },
1169+
];
1170+
1171+
it('looks up the project form fields and sends the normalized case_type in the request body', async () => {
1172+
fetchFormFieldsMock.mockResolvedValue({
1173+
default_fields: { case_type: { values: caseTypeValues } },
1174+
custom_fields: {},
1175+
});
1176+
normalizeMock.mockReturnValue('Functional');
1177+
(apiClientMock.post as Mock).mockResolvedValueOnce(createSuccess);
1178+
1179+
const result = await createTestCaseReal(
1180+
{ ...baseArgs, case_type: 'functional' },
1181+
mockConfig as any,
1182+
);
1183+
1184+
expect(result.isError).toBeFalsy();
1185+
expect(normalizeMock).toHaveBeenCalledWith(caseTypeValues, 'functional', 'name');
1186+
const body = (apiClientMock.post as Mock).mock.calls[0][0].body;
1187+
expect(body.test_case.case_type).toBe('Functional');
1188+
});
1189+
1190+
it('omits case_type from the request body when not provided (preserves project default)', async () => {
1191+
(apiClientMock.post as Mock).mockResolvedValueOnce(createSuccess);
1192+
1193+
await createTestCaseReal({ ...baseArgs }, mockConfig as any);
1194+
1195+
const body = (apiClientMock.post as Mock).mock.calls[0][0].body;
1196+
expect(body.test_case).not.toHaveProperty('case_type');
1197+
});
1198+
1199+
it('passes the raw case_type through when the form-fields lookup fails (graceful fallback)', async () => {
1200+
fetchFormFieldsMock.mockRejectedValue(new Error('Network Error'));
1201+
(apiClientMock.post as Mock).mockResolvedValueOnce(createSuccess);
1202+
1203+
await createTestCaseReal(
1204+
{ ...baseArgs, case_type: 'functional' },
1205+
mockConfig as any,
1206+
);
1207+
1208+
const body = (apiClientMock.post as Mock).mock.calls[0][0].body;
1209+
expect(body.test_case.case_type).toBe('functional');
1210+
});
1211+
1212+
it('normalizes both priority and case_type in a single form-fields lookup', async () => {
1213+
fetchFormFieldsMock.mockResolvedValue({
1214+
default_fields: {
1215+
priority: { values: priorityValues },
1216+
case_type: { values: caseTypeValues },
1217+
},
1218+
custom_fields: {},
1219+
});
1220+
normalizeMock.mockImplementation((_values, input) =>
1221+
input === 'critical' ? 'Critical' : 'Functional',
1222+
);
1223+
(apiClientMock.post as Mock).mockResolvedValueOnce(createSuccess);
1224+
1225+
await createTestCaseReal(
1226+
{ ...baseArgs, priority: 'critical', case_type: 'functional' },
1227+
mockConfig as any,
1228+
);
1229+
1230+
expect(fetchFormFieldsMock).toHaveBeenCalledTimes(1);
1231+
const body = (apiClientMock.post as Mock).mock.calls[0][0].body;
1232+
expect(body.test_case.priority).toBe('Critical');
1233+
expect(body.test_case.case_type).toBe('Functional');
1234+
});
11651235
});
11661236

11671237
// Template slug pass-through + multi-select custom fields.

0 commit comments

Comments
 (0)