@smartbear/mcp 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bugsnag/client/api/Error.js +52 -14
- package/dist/bugsnag/client/api/Project.js +193 -8
- package/dist/bugsnag/client/api/api.js +130 -3
- package/dist/bugsnag/client/api/base.js +19 -0
- package/dist/bugsnag/client/api/index.js +1 -1
- package/dist/bugsnag/client.js +527 -20
- package/dist/bugsnag/input-schemas.js +14 -6
- package/dist/common/transport-stdio.js +5 -0
- package/dist/qmetry/client/auto-resolve.js +10 -0
- package/dist/qmetry/client/automation.js +171 -0
- package/dist/qmetry/client/handlers.js +9 -2
- package/dist/qmetry/client/project.js +87 -1
- package/dist/qmetry/client/testsuite.js +37 -1
- package/dist/qmetry/client/tools/automation-tools.js +290 -0
- package/dist/qmetry/client/tools/index.js +3 -0
- package/dist/qmetry/client/tools/issue-tools.js +1 -1
- package/dist/qmetry/client/tools/project-tools.js +311 -1
- package/dist/qmetry/client/tools/requirement-tools.js +1 -1
- package/dist/qmetry/client/tools/testcase-tools.js +311 -39
- package/dist/qmetry/client/tools/testsuite-tools.js +337 -23
- package/dist/qmetry/config/constants.js +6 -0
- package/dist/qmetry/config/rest-endpoints.js +8 -0
- package/dist/qmetry/types/automation.js +8 -0
- package/dist/qmetry/types/common.js +299 -4
- package/dist/qmetry/types/issues.js +5 -0
- package/dist/qmetry/types/project.js +13 -0
- package/dist/qmetry/types/requirements.js +5 -0
- package/dist/qmetry/types/testcase.js +5 -0
- package/dist/qmetry/types/testsuite.js +9 -0
- package/dist/swagger/client/api.js +93 -36
- package/dist/swagger/client/configuration.js +3 -1
- package/dist/swagger/client/portal-types.js +7 -6
- package/dist/swagger/client/registry-types.js +26 -0
- package/dist/swagger/client/tools.js +15 -16
- package/dist/swagger/client.js +6 -6
- package/dist/tests/unit/bugsnag/utils/factories.js +86 -0
- package/dist/zephyr/client.js +4 -0
- package/dist/zephyr/tool/environment/get-environments.js +68 -0
- package/dist/zephyr/tool/test-execution/get-test-executions.js +45 -0
- package/package.json +3 -2
|
@@ -21,6 +21,25 @@ export const DEFAULT_FOLDER_OPTIONS = {
|
|
|
21
21
|
restoreDefaultColumns: false,
|
|
22
22
|
folderID: null,
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Entity types supported for automation result import
|
|
26
|
+
*/
|
|
27
|
+
export const EntityTypeEnum = z.enum([
|
|
28
|
+
"TESTNG",
|
|
29
|
+
"CUCUMBER",
|
|
30
|
+
"JUNIT",
|
|
31
|
+
"HPUFT",
|
|
32
|
+
"QAF",
|
|
33
|
+
"ROBOT",
|
|
34
|
+
]);
|
|
35
|
+
/**
|
|
36
|
+
* Automation hierarchy options for TestNG and JUnit
|
|
37
|
+
*/
|
|
38
|
+
export const AutomationHierarchyEnum = z.enum(["1", "2", "3"]);
|
|
39
|
+
/**
|
|
40
|
+
* Skip warning options
|
|
41
|
+
*/
|
|
42
|
+
export const SkipWarningEnum = z.enum(["0", "1"]);
|
|
24
43
|
// Reusable Zod schema components
|
|
25
44
|
export const CommonFields = {
|
|
26
45
|
projectKey: z
|
|
@@ -288,12 +307,94 @@ export const PlatformArgsSchema = z.object({
|
|
|
288
307
|
.default('[{"property":"platformID","direction":"DESC"}]'),
|
|
289
308
|
filter: CommonFields.filter,
|
|
290
309
|
});
|
|
310
|
+
export const CreateReleaseArgsSchema = z.object({
|
|
311
|
+
projectKey: CommonFields.projectKeyOptional,
|
|
312
|
+
baseUrl: CommonFields.baseUrl,
|
|
313
|
+
release: z.object({
|
|
314
|
+
name: z.string().describe("Release name (required)"),
|
|
315
|
+
description: z.string().optional().describe("Release description"),
|
|
316
|
+
startDate: z
|
|
317
|
+
.string()
|
|
318
|
+
.optional()
|
|
319
|
+
.describe("Release start date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
|
|
320
|
+
targetDate: z
|
|
321
|
+
.string()
|
|
322
|
+
.optional()
|
|
323
|
+
.describe("Release target/end date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
|
|
324
|
+
projectID: z
|
|
325
|
+
.number()
|
|
326
|
+
.optional()
|
|
327
|
+
.describe("Project ID (optional, can be auto-resolved from project key if not provided)"),
|
|
328
|
+
}),
|
|
329
|
+
cycle: z
|
|
330
|
+
.object({
|
|
331
|
+
name: z.string().describe("Cycle name (required if cycle is provided)"),
|
|
332
|
+
isLocked: z
|
|
333
|
+
.boolean()
|
|
334
|
+
.optional()
|
|
335
|
+
.describe("Whether the cycle is locked (default: false)"),
|
|
336
|
+
isArchived: z
|
|
337
|
+
.boolean()
|
|
338
|
+
.optional()
|
|
339
|
+
.describe("Whether the cycle is archived (default: false)"),
|
|
340
|
+
})
|
|
341
|
+
.optional()
|
|
342
|
+
.describe("Optional cycle to create within the release"),
|
|
343
|
+
});
|
|
344
|
+
export const CreateCycleArgsSchema = z.object({
|
|
345
|
+
projectKey: CommonFields.projectKeyOptional,
|
|
346
|
+
baseUrl: CommonFields.baseUrl,
|
|
347
|
+
cycle: z.object({
|
|
348
|
+
name: z.string().describe("Cycle name (required)"),
|
|
349
|
+
startDate: z
|
|
350
|
+
.string()
|
|
351
|
+
.optional()
|
|
352
|
+
.describe("Cycle start date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
|
|
353
|
+
targetDate: z
|
|
354
|
+
.string()
|
|
355
|
+
.optional()
|
|
356
|
+
.describe("Cycle target/end date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
|
|
357
|
+
projectID: z
|
|
358
|
+
.number()
|
|
359
|
+
.optional()
|
|
360
|
+
.describe("Project ID (optional, can be auto-resolved from project key if not provided)"),
|
|
361
|
+
releaseID: z
|
|
362
|
+
.number()
|
|
363
|
+
.describe("Release ID (required) - the release this cycle belongs to"),
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
export const UpdateCycleArgsSchema = z.object({
|
|
367
|
+
projectKey: CommonFields.projectKeyOptional,
|
|
368
|
+
baseUrl: CommonFields.baseUrl,
|
|
369
|
+
cycle: z.object({
|
|
370
|
+
name: z.string().optional().describe("Cycle name (optional for update)"),
|
|
371
|
+
startDate: z
|
|
372
|
+
.string()
|
|
373
|
+
.optional()
|
|
374
|
+
.describe("Cycle start date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
|
|
375
|
+
targetDate: z
|
|
376
|
+
.string()
|
|
377
|
+
.optional()
|
|
378
|
+
.describe("Cycle target/end date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
|
|
379
|
+
buildID: z
|
|
380
|
+
.number()
|
|
381
|
+
.describe("Build ID (required for identifying the cycle to update). " +
|
|
382
|
+
"To get the buildID - Call API 'Cycle/List' (FETCH_RELEASES_CYCLES tool). " +
|
|
383
|
+
"From the response, get value of following attribute -> data[<index>].buildID"),
|
|
384
|
+
releaseID: z
|
|
385
|
+
.number()
|
|
386
|
+
.describe("Release ID (required for identifying the cycle to update). " +
|
|
387
|
+
"To get the releaseID - Call API 'Cycle/List' (FETCH_RELEASES_CYCLES tool). " +
|
|
388
|
+
"From the response, get value of following attribute -> data[<index>].releaseID"),
|
|
389
|
+
}),
|
|
390
|
+
});
|
|
291
391
|
export const CreateTestCaseStepSchema = z.object({
|
|
292
392
|
orderId: z.number(),
|
|
293
393
|
description: z.string(),
|
|
294
394
|
inputData: z.string().optional(),
|
|
295
395
|
expectedOutcome: z.string().optional(),
|
|
296
396
|
UDF: z.record(z.string()).optional(),
|
|
397
|
+
tcStepID: z.number().optional(), // Required for updating existing steps, omit for new steps
|
|
297
398
|
});
|
|
298
399
|
export const UpdateTestCaseRemoveStepSchema = z.object({
|
|
299
400
|
tcID: z.number(),
|
|
@@ -336,9 +437,40 @@ export const UpdateTestCaseArgsSchema = z.object({
|
|
|
336
437
|
baseUrl: CommonFields.baseUrl,
|
|
337
438
|
tcID: CommonFields.tcID,
|
|
338
439
|
tcVersionID: CommonFields.tcVersionID,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
440
|
+
tcVersion: z
|
|
441
|
+
.number()
|
|
442
|
+
.optional()
|
|
443
|
+
.describe("Test Case version number (required when withVersion=true for creating new version). " +
|
|
444
|
+
"This is the current version number from which a new version will be created."),
|
|
445
|
+
withVersion: z
|
|
446
|
+
.boolean()
|
|
447
|
+
.optional()
|
|
448
|
+
.describe("Pass 'true' if you want to create a new version of the test case with incremented version number. " +
|
|
449
|
+
"When true, a new version is created (e.g., if current version is 2, new version 3 is created). " +
|
|
450
|
+
"When false or omitted, updates the existing version specified by tcVersionID. " +
|
|
451
|
+
"IMPORTANT: Always send proper tcVersionID to identify which version the request is for."),
|
|
452
|
+
versionComment: z
|
|
453
|
+
.string()
|
|
454
|
+
.optional()
|
|
455
|
+
.describe("Comment or description for the new version (used only when withVersion=true). " +
|
|
456
|
+
"Helps track what changed in this new version. Example: 'Updated test steps for new requirements'"),
|
|
457
|
+
notruncurrent: z
|
|
458
|
+
.boolean()
|
|
459
|
+
.optional()
|
|
460
|
+
.describe("Flag to control execution behavior for current version when creating a new version. " +
|
|
461
|
+
"Used in conjunction with withVersion=true."),
|
|
462
|
+
notrunall: z
|
|
463
|
+
.boolean()
|
|
464
|
+
.optional()
|
|
465
|
+
.describe("Flag to control execution behavior for all versions when creating a new version. " +
|
|
466
|
+
"Used in conjunction with withVersion=true."),
|
|
467
|
+
folderPath: CommonFields.tsFolderPath,
|
|
468
|
+
scope: CommonFields.scope,
|
|
469
|
+
isStepUpdated: z
|
|
470
|
+
.boolean()
|
|
471
|
+
.optional()
|
|
472
|
+
.describe("Set to true when steps are being added, updated, or removed. " +
|
|
473
|
+
"Required when including 'steps' or 'removeSteps' arrays."),
|
|
342
474
|
steps: z.array(CreateTestCaseStepSchema).optional(),
|
|
343
475
|
removeSteps: z.array(UpdateTestCaseRemoveStepSchema).optional(),
|
|
344
476
|
name: z.string().optional(),
|
|
@@ -347,10 +479,18 @@ export const UpdateTestCaseArgsSchema = z.object({
|
|
|
347
479
|
owner: z.number().optional(),
|
|
348
480
|
testCaseState: z.number().optional(),
|
|
349
481
|
testCaseType: z.number().optional(),
|
|
482
|
+
estimatedTime: z
|
|
483
|
+
.number()
|
|
484
|
+
.optional()
|
|
485
|
+
.describe("Estimated execution time in seconds. Example: 7200 for 2 hours"),
|
|
350
486
|
executionMinutes: z.number().optional(),
|
|
351
487
|
testingType: z.number().optional(),
|
|
352
488
|
description: z.string().optional(),
|
|
353
|
-
updateOnlyMetadata: z
|
|
489
|
+
updateOnlyMetadata: z
|
|
490
|
+
.boolean()
|
|
491
|
+
.optional()
|
|
492
|
+
.describe("Set to true to update only metadata fields without touching test steps. " +
|
|
493
|
+
"When true, steps and removeSteps are ignored."),
|
|
354
494
|
});
|
|
355
495
|
export const TestCaseListArgsSchema = z.object({
|
|
356
496
|
projectKey: CommonFields.projectKeyOptional,
|
|
@@ -762,3 +902,158 @@ export const LinkPlatformsToTestSuiteArgsSchema = z.object({
|
|
|
762
902
|
.string()
|
|
763
903
|
.describe("Comma-separated value of PlatformId (required). To get the qmPlatformId - Call API 'Platform/List' From the response, get value of following attribute -> data[<index>].platformID"),
|
|
764
904
|
});
|
|
905
|
+
// Export for Bulk Update Test Case Execution Status tool
|
|
906
|
+
export const BulkUpdateExecutionStatusArgsSchema = z.object({
|
|
907
|
+
projectKey: CommonFields.projectKeyOptional,
|
|
908
|
+
baseUrl: CommonFields.baseUrl,
|
|
909
|
+
entityIDs: z
|
|
910
|
+
.string()
|
|
911
|
+
.describe("Comma-separated IDs of Test Case Runs to update (e.g., '66095087' for single or '66095069,66095075' for bulk). " +
|
|
912
|
+
"To get the entityIDs - Call API 'Execution/Fetch Testcase Run ID' " +
|
|
913
|
+
"From the response, get value of following attribute -> data[<index>].tcRunID"),
|
|
914
|
+
entityType: z
|
|
915
|
+
.enum(["TCR", "TCSR"])
|
|
916
|
+
.describe("Type of Entity to Execute: 'TCR' (Test Case Run) or 'TCSR' (Test Case Step Run)")
|
|
917
|
+
.default("TCR"),
|
|
918
|
+
qmTsRunId: z
|
|
919
|
+
.string()
|
|
920
|
+
.describe("Id of Test Suite Run to execute (required). " +
|
|
921
|
+
"To get the qmTsRunId - Call API 'Execution/Fetch Executions' " +
|
|
922
|
+
"From the response, get value of following attribute -> data[<index>].tsRunID"),
|
|
923
|
+
runStatusID: z
|
|
924
|
+
.number()
|
|
925
|
+
.describe("Id of the execution status to set (required). " +
|
|
926
|
+
"To get the runStatusID - Call API 'Admin/Project GET info Service' " +
|
|
927
|
+
"From the response, get value of following attribute -> allstatus[<index>].id " +
|
|
928
|
+
"Common statuses: Pass, Fail, Not Run, Blocked, WIP, etc."),
|
|
929
|
+
dropID: z
|
|
930
|
+
.union([z.number(), z.string()])
|
|
931
|
+
.optional()
|
|
932
|
+
.describe("Unique identifier of drop/build on which execution is to be performed (optional). " +
|
|
933
|
+
"To get the dropID - Call API 'Fetch Build/List' " +
|
|
934
|
+
"From the response, get value of following attribute -> data[<index>].dropID"),
|
|
935
|
+
isAutoExecuted: z
|
|
936
|
+
.enum(["0", "1"])
|
|
937
|
+
.optional()
|
|
938
|
+
.describe("Set '1' for automated and '0' for manual Execution Type"),
|
|
939
|
+
isBulkOperation: z
|
|
940
|
+
.boolean()
|
|
941
|
+
.optional()
|
|
942
|
+
.describe("Set true for bulk operations (multiple entityIDs), false for single execution update. " +
|
|
943
|
+
"Default: true if multiple comma-separated entityIDs, false otherwise"),
|
|
944
|
+
comments: z
|
|
945
|
+
.string()
|
|
946
|
+
.optional()
|
|
947
|
+
.describe("Optional comments for the execution status update"),
|
|
948
|
+
username: z
|
|
949
|
+
.string()
|
|
950
|
+
.optional()
|
|
951
|
+
.describe("If Part 11 Compliance is active then required for authentication"),
|
|
952
|
+
password: z
|
|
953
|
+
.string()
|
|
954
|
+
.optional()
|
|
955
|
+
.describe("If Part 11 Compliance is active then required for authentication"),
|
|
956
|
+
qmRunObj: z
|
|
957
|
+
.string()
|
|
958
|
+
.optional()
|
|
959
|
+
.describe("Internal QMetry run object (optional, usually empty string)"),
|
|
960
|
+
type: z
|
|
961
|
+
.enum(["TCR", "TCSR"])
|
|
962
|
+
.optional()
|
|
963
|
+
.describe("Type of Entity - same as entityType (for backwards compatibility)"),
|
|
964
|
+
});
|
|
965
|
+
/**
|
|
966
|
+
* Import automation results payload schema
|
|
967
|
+
*
|
|
968
|
+
* CRITICAL: File upload is required and must be provided by the user
|
|
969
|
+
* User should upload a valid result file (.json, .xml, or .zip up to 30 MB)
|
|
970
|
+
*/
|
|
971
|
+
export const ImportAutomationResultsPayloadSchema = z.object({
|
|
972
|
+
// REQUIRED: File data as base64 string or file path
|
|
973
|
+
file: z
|
|
974
|
+
.string()
|
|
975
|
+
.refine((val) => {
|
|
976
|
+
// Base64 regex: matches typical base64 strings (not perfect, but covers most cases)
|
|
977
|
+
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
978
|
+
// File path regex: ends with .json, .xml, or .zip (case-insensitive)
|
|
979
|
+
const filePathRegex = /\.(json|xml|zip)$/i;
|
|
980
|
+
return base64Regex.test(val) || filePathRegex.test(val);
|
|
981
|
+
}, {
|
|
982
|
+
message: "Must be a valid base64 string or a file path ending with .json, .xml, or .zip",
|
|
983
|
+
})
|
|
984
|
+
.describe("Base64 encoded file content or file path. User must upload result file (.json, .xml, .zip up to 30 MB)"),
|
|
985
|
+
// REQUIRED: Original filename with extension
|
|
986
|
+
fileName: z
|
|
987
|
+
.string()
|
|
988
|
+
.describe("Original filename with extension (.json, .xml, or .zip)"),
|
|
989
|
+
// REQUIRED: Entity type (format of result file)
|
|
990
|
+
entityType: EntityTypeEnum.describe("Format of result file: TESTNG, CUCUMBER, JUNIT, HPUFT, QAF, or ROBOT"),
|
|
991
|
+
// OPTIONAL: Automation hierarchy (applies to TestNG and JUnit only)
|
|
992
|
+
automationHierarchy: AutomationHierarchyEnum.optional().describe("TestNG/JUnit hierarchy: 1=Test Case-Test Step, 2=Test Case only, 3=Test Suite-Test Case. Default: 1"),
|
|
993
|
+
// OPTIONAL: Test suite name
|
|
994
|
+
testsuiteName: z
|
|
995
|
+
.string()
|
|
996
|
+
.optional()
|
|
997
|
+
.describe("Custom test suite name. Ignored if automationHierarchy=3 for JUnit or =2 for ROBOT"),
|
|
998
|
+
// OPTIONAL: Test suite ID (reuse existing)
|
|
999
|
+
testsuiteId: z
|
|
1000
|
+
.string()
|
|
1001
|
+
.optional()
|
|
1002
|
+
.describe("Reuse existing Test Suite by ID or Entity Key. Ignored if automationHierarchy=3 for JUnit or =2 for ROBOT"),
|
|
1003
|
+
// OPTIONAL: Test suite folder path
|
|
1004
|
+
tsFolderPath: z
|
|
1005
|
+
.string()
|
|
1006
|
+
.optional()
|
|
1007
|
+
.describe("Test suite folder path. Creates folder if doesn't exist. Ignored if reusing test suite"),
|
|
1008
|
+
// OPTIONAL: Test case folder path
|
|
1009
|
+
tcFolderPath: z
|
|
1010
|
+
.string()
|
|
1011
|
+
.optional()
|
|
1012
|
+
.describe("Test case folder path. Creates folder if doesn't exist. Ignored if reusing test case"),
|
|
1013
|
+
// OPTIONAL: Platform ID or name
|
|
1014
|
+
platformID: z
|
|
1015
|
+
.string()
|
|
1016
|
+
.optional()
|
|
1017
|
+
.describe("Platform ID or Platform Name. Default: 'No Platform'"),
|
|
1018
|
+
// OPTIONAL: Project ID or key (overrides header project)
|
|
1019
|
+
projectID: z
|
|
1020
|
+
.string()
|
|
1021
|
+
.optional()
|
|
1022
|
+
.describe("Project ID, Project Key, or Project name. Overrides project in header"),
|
|
1023
|
+
// OPTIONAL: Release ID or name
|
|
1024
|
+
releaseID: z
|
|
1025
|
+
.string()
|
|
1026
|
+
.optional()
|
|
1027
|
+
.describe("Release ID or Release name. Requires projectID if provided"),
|
|
1028
|
+
// OPTIONAL: Cycle ID or name
|
|
1029
|
+
cycleID: z
|
|
1030
|
+
.string()
|
|
1031
|
+
.optional()
|
|
1032
|
+
.describe("Cycle ID or Cycle name. Requires releaseID and projectID if provided"),
|
|
1033
|
+
// OPTIONAL: Build ID or name
|
|
1034
|
+
buildID: z.string().optional().describe("Build ID or Build name"),
|
|
1035
|
+
// OPTIONAL: Test case fields (JSON format)
|
|
1036
|
+
testcase_fields: z
|
|
1037
|
+
.string()
|
|
1038
|
+
.optional()
|
|
1039
|
+
.describe('JSON string with test case system fields and UDFs. Ignored if reusing test case. Example: {"component":["com1"], "priority":"High"}'),
|
|
1040
|
+
// OPTIONAL: Test suite fields (JSON format)
|
|
1041
|
+
testsuite_fields: z
|
|
1042
|
+
.string()
|
|
1043
|
+
.optional()
|
|
1044
|
+
.describe('JSON string with test suite system fields and UDFs. Ignored if reusing test suite. Example: {"testSuiteState":"Open", "testsuiteOwner":"user"}'),
|
|
1045
|
+
// OPTIONAL: Skip warning about summary length
|
|
1046
|
+
skipWarning: SkipWarningEnum.optional().describe("0=Fail if summary >255 chars, 1=Truncate summary to 255 chars. Default: 0"),
|
|
1047
|
+
// OPTIONAL: Matching requirement for test cases
|
|
1048
|
+
is_matching_required: z
|
|
1049
|
+
.string()
|
|
1050
|
+
.optional()
|
|
1051
|
+
.describe("True=Create new TC if summary/steps don't match, False=Reuse linked TC. Default: True"),
|
|
1052
|
+
});
|
|
1053
|
+
export const FetchAutomationStatusPayloadSchema = z.object({
|
|
1054
|
+
projectKey: CommonFields.projectKeyOptional,
|
|
1055
|
+
baseUrl: CommonFields.baseUrl,
|
|
1056
|
+
requestID: z
|
|
1057
|
+
.number()
|
|
1058
|
+
.describe("Numeric request ID from import automation response"),
|
|
1059
|
+
});
|
|
@@ -13,4 +13,9 @@ export const DEFAULT_FETCH_ISSUES_PAYLOAD = {
|
|
|
13
13
|
...DEFAULT_SORT,
|
|
14
14
|
udfFilter: "[]",
|
|
15
15
|
isJiraIntegated: false,
|
|
16
|
+
/**
|
|
17
|
+
* Prevents filter persistence in the QMetry web application UI.
|
|
18
|
+
* Always set to false to ensure filters are not saved when fetching test cases via API.
|
|
19
|
+
*/
|
|
20
|
+
isFilterSaveRequired: false,
|
|
16
21
|
};
|
|
@@ -15,3 +15,16 @@ export const DEFAULT_FETCH_PLATFORMS_PAYLOAD = {
|
|
|
15
15
|
...DEFAULT_FILTER,
|
|
16
16
|
...DEFAULT_SORT,
|
|
17
17
|
};
|
|
18
|
+
export const DEFAULT_CREATE_RELEASE_PAYLOAD = {
|
|
19
|
+
release: {
|
|
20
|
+
name: "Default Release",
|
|
21
|
+
},
|
|
22
|
+
cycle: {
|
|
23
|
+
name: "Default Cycle",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export const DEFAULT_CREATE_CYCLE_PAYLOAD = {
|
|
27
|
+
cycle: {
|
|
28
|
+
name: "Default Cycle",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -7,6 +7,11 @@ export const DEFAULT_FETCH_REQUIREMENTS_PAYLOAD = {
|
|
|
7
7
|
udfFilter: "[]",
|
|
8
8
|
isJiraFilter: false,
|
|
9
9
|
filterType: "QMETRY",
|
|
10
|
+
/**
|
|
11
|
+
* Prevents filter persistence in the QMetry web application UI.
|
|
12
|
+
* Always set to false to ensure filters are not saved when fetching test cases via API.
|
|
13
|
+
*/
|
|
14
|
+
isFilterSaveRequired: false,
|
|
10
15
|
};
|
|
11
16
|
export const DEFAULT_FETCH_REQUIREMENT_DETAILS_PAYLOAD = {
|
|
12
17
|
// No defaults needed for this simple payload
|
|
@@ -4,6 +4,11 @@ export const DEFAULT_FETCH_TESTCASES_PAYLOAD = {
|
|
|
4
4
|
...DEFAULT_FILTER,
|
|
5
5
|
...DEFAULT_FOLDER_OPTIONS,
|
|
6
6
|
udfFilter: "[]",
|
|
7
|
+
/**
|
|
8
|
+
* Prevents filter persistence in the QMetry web application UI.
|
|
9
|
+
* Always set to false to ensure filters are not saved when fetching test cases via API.
|
|
10
|
+
*/
|
|
11
|
+
isFilterSaveRequired: false,
|
|
7
12
|
};
|
|
8
13
|
export const DEFAULT_CREATE_TESTCASES_PAYLOAD = {
|
|
9
14
|
scope: "project",
|
|
@@ -40,5 +40,14 @@ export const DEFAULT_FETCH_TESTSUITES_PAYLOAD = {
|
|
|
40
40
|
...DEFAULT_SORT,
|
|
41
41
|
udfFilter: "[]",
|
|
42
42
|
scope: "cycle",
|
|
43
|
+
/**
|
|
44
|
+
* Prevents filter persistence in the QMetry web application UI.
|
|
45
|
+
* Always set to false to ensure filters are not saved when fetching test cases via API.
|
|
46
|
+
*/
|
|
47
|
+
isFilterSaveRequired: false,
|
|
43
48
|
};
|
|
44
49
|
export const DEFAULT_LINKED_PLATFORMS_TO_TESTSUITE_PAYLOAD = {};
|
|
50
|
+
export const DEFAULT_BULK_UPDATE_EXECUTION_STATUS_PAYLOAD = {
|
|
51
|
+
entityType: "TCR",
|
|
52
|
+
qmRunObj: "",
|
|
53
|
+
};
|
|
@@ -7,6 +7,24 @@ import { ToolError } from "../../common/types.js";
|
|
|
7
7
|
// - group 3: name
|
|
8
8
|
// - group 4: version
|
|
9
9
|
const SWAGGER_URL_REGEX = /\/(apis|domains|templates)\/([^/]+)\/([^/]+)\/([^/]+)/;
|
|
10
|
+
/**
|
|
11
|
+
* Type guard to check if a value has an 'id' property
|
|
12
|
+
*/
|
|
13
|
+
function hasId(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && "id" in value;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Type guard to check if a value has a 'message' property
|
|
18
|
+
*/
|
|
19
|
+
function hasMessage(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && "message" in value;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Type guard to check if a value has an 'errorsFound' property
|
|
24
|
+
*/
|
|
25
|
+
function hasErrorsFound(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && "errorsFound" in value;
|
|
27
|
+
}
|
|
10
28
|
export class SwaggerAPI {
|
|
11
29
|
config;
|
|
12
30
|
headers;
|
|
@@ -118,7 +136,7 @@ export class SwaggerAPI {
|
|
|
118
136
|
body: JSON.stringify(body),
|
|
119
137
|
});
|
|
120
138
|
const result = await this.handleResponse(response);
|
|
121
|
-
if (!(
|
|
139
|
+
if (!hasId(result)) {
|
|
122
140
|
throw new Error("Unexpected empty response creating portal");
|
|
123
141
|
}
|
|
124
142
|
return result;
|
|
@@ -129,21 +147,11 @@ export class SwaggerAPI {
|
|
|
129
147
|
headers: this.headers,
|
|
130
148
|
});
|
|
131
149
|
const result = await this.handleResponse(response);
|
|
132
|
-
if (!(
|
|
150
|
+
if (!hasId(result)) {
|
|
133
151
|
throw new ToolError("Portal not found or empty response");
|
|
134
152
|
}
|
|
135
153
|
return result;
|
|
136
154
|
}
|
|
137
|
-
async deletePortal(portalId) {
|
|
138
|
-
const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}`, {
|
|
139
|
-
method: "DELETE",
|
|
140
|
-
headers: this.headers,
|
|
141
|
-
});
|
|
142
|
-
if (!response.ok) {
|
|
143
|
-
const errorText = await response.text().catch(() => "");
|
|
144
|
-
throw new ToolError(`Swagger deletePortal failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
155
|
async updatePortal(portalId, body) {
|
|
148
156
|
const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}`, {
|
|
149
157
|
method: "PATCH",
|
|
@@ -167,7 +175,7 @@ export class SwaggerAPI {
|
|
|
167
175
|
body: JSON.stringify(body),
|
|
168
176
|
});
|
|
169
177
|
const result = await this.handleResponse(response);
|
|
170
|
-
if (!(
|
|
178
|
+
if (!hasId(result)) {
|
|
171
179
|
throw new Error("Unexpected empty response creating product");
|
|
172
180
|
}
|
|
173
181
|
return result;
|
|
@@ -178,7 +186,7 @@ export class SwaggerAPI {
|
|
|
178
186
|
headers: this.headers,
|
|
179
187
|
});
|
|
180
188
|
const result = await this.handleResponse(response);
|
|
181
|
-
if (!(
|
|
189
|
+
if (!hasId(result)) {
|
|
182
190
|
throw new ToolError("Product not found or empty response");
|
|
183
191
|
}
|
|
184
192
|
return result;
|
|
@@ -320,24 +328,6 @@ export class SwaggerAPI {
|
|
|
320
328
|
}
|
|
321
329
|
return { success: true };
|
|
322
330
|
}
|
|
323
|
-
/**
|
|
324
|
-
* Delete document
|
|
325
|
-
* @param args - Parameters for deleting document
|
|
326
|
-
* @returns Success response
|
|
327
|
-
*/
|
|
328
|
-
async deleteDocument(args) {
|
|
329
|
-
const { documentId } = args;
|
|
330
|
-
const url = `${this.config.portalBasePath}/documents/${documentId}`;
|
|
331
|
-
const response = await fetch(url, {
|
|
332
|
-
method: "DELETE",
|
|
333
|
-
headers: this.headers,
|
|
334
|
-
});
|
|
335
|
-
if (!response.ok) {
|
|
336
|
-
const errorText = await response.text();
|
|
337
|
-
throw new Error(`API Hub deleteDocument failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
|
|
338
|
-
}
|
|
339
|
-
return { success: true };
|
|
340
|
-
}
|
|
341
331
|
/**
|
|
342
332
|
* Delete table of contents entry
|
|
343
333
|
* @param args - Parameters for deleting table of contents entry
|
|
@@ -519,7 +509,7 @@ export class SwaggerAPI {
|
|
|
519
509
|
owner: params.owner,
|
|
520
510
|
apiName: params.apiName,
|
|
521
511
|
version: "1.0.0",
|
|
522
|
-
url:
|
|
512
|
+
url: `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}/1.0.0`,
|
|
523
513
|
operation,
|
|
524
514
|
};
|
|
525
515
|
}
|
|
@@ -551,7 +541,48 @@ export class SwaggerAPI {
|
|
|
551
541
|
owner: params.owner,
|
|
552
542
|
apiName: params.apiName,
|
|
553
543
|
template: params.template,
|
|
554
|
-
url:
|
|
544
|
+
url: `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}`,
|
|
545
|
+
operation,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Create API from Prompt using SmartBear AI
|
|
550
|
+
* @param params Parameters for creating API from prompt including owner, api name, prompt, and specification type
|
|
551
|
+
* @returns Created API metadata with URL. HTTP 201 indicates creation, HTTP 200 for update, HTTP 205 for reload
|
|
552
|
+
*/
|
|
553
|
+
async createApiFromPrompt(params) {
|
|
554
|
+
// Construct the URL with query parameters
|
|
555
|
+
const searchParams = new URLSearchParams();
|
|
556
|
+
const specType = params.specType ?? "openapi30x";
|
|
557
|
+
searchParams.append("specType", specType);
|
|
558
|
+
const url = `${this.config.registryBasePath}/apis/${encodeURIComponent(params.owner)}/${encodeURIComponent(params.apiName)}/.ai?${searchParams.toString()}`;
|
|
559
|
+
// Use POST method with JSON body containing the prompt
|
|
560
|
+
const response = await fetch(url, {
|
|
561
|
+
method: "POST",
|
|
562
|
+
headers: {
|
|
563
|
+
...this.headers,
|
|
564
|
+
"Content-Type": "application/json",
|
|
565
|
+
},
|
|
566
|
+
body: JSON.stringify(params.prompt),
|
|
567
|
+
});
|
|
568
|
+
if (!response.ok) {
|
|
569
|
+
const errorText = await response.text().catch(() => "");
|
|
570
|
+
throw new ToolError(`SwaggerHub Registry API createApiFromPrompt failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
|
|
571
|
+
}
|
|
572
|
+
// Determine operation type based on HTTP status code
|
|
573
|
+
// 201 = new API created, 200 = existing API updated, 205 = API saved and should be reloaded
|
|
574
|
+
const operation = response.status === 201 ? "create" : "update";
|
|
575
|
+
// Extract version from X-Version header
|
|
576
|
+
const version = response.headers.get("X-Version");
|
|
577
|
+
// Return formatted response with the required fields
|
|
578
|
+
return {
|
|
579
|
+
owner: params.owner,
|
|
580
|
+
apiName: params.apiName,
|
|
581
|
+
specType: specType,
|
|
582
|
+
version: version || undefined,
|
|
583
|
+
url: version
|
|
584
|
+
? `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}/${version}`
|
|
585
|
+
: `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}`,
|
|
555
586
|
operation,
|
|
556
587
|
};
|
|
557
588
|
}
|
|
@@ -595,7 +626,7 @@ export class SwaggerAPI {
|
|
|
595
626
|
requestBody = JSON.stringify(parsedDefinition);
|
|
596
627
|
}
|
|
597
628
|
catch (error) {
|
|
598
|
-
throw new
|
|
629
|
+
throw new ToolError(`Invalid JSON format in definition: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
599
630
|
}
|
|
600
631
|
}
|
|
601
632
|
const url = `${this.config.registryBasePath}/standardization/${encodeURIComponent(params.orgName)}/scan`;
|
|
@@ -609,8 +640,34 @@ export class SwaggerAPI {
|
|
|
609
640
|
});
|
|
610
641
|
if (!response.ok) {
|
|
611
642
|
const errorText = await response.text().catch(() => "");
|
|
612
|
-
throw new
|
|
643
|
+
throw new ToolError(`SwaggerHub Registry API scanStandardization failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
|
|
613
644
|
}
|
|
614
645
|
return await this.handleResponse(response);
|
|
615
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Standardize and fix an API definition using AI
|
|
649
|
+
* @param params Parameters including owner, API name, and version
|
|
650
|
+
* @returns Standardization response with status and fixed definition
|
|
651
|
+
*/
|
|
652
|
+
async standardizeApi(params) {
|
|
653
|
+
const url = `${this.config.registryBasePath}/apis/${encodeURIComponent(params.owner)}/${encodeURIComponent(params.api)}/${encodeURIComponent(params.version)}/standardize`;
|
|
654
|
+
const response = await fetch(url, {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers: this.headers,
|
|
657
|
+
});
|
|
658
|
+
if (!response.ok) {
|
|
659
|
+
const errorText = await response.text().catch(() => "");
|
|
660
|
+
throw new ToolError(`SwaggerHub Registry API standardizeApi failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
|
|
661
|
+
}
|
|
662
|
+
const result = await this.handleResponse(response);
|
|
663
|
+
// Validate that we have the expected response structure
|
|
664
|
+
if (!hasMessage(result)) {
|
|
665
|
+
throw new ToolError("Unexpected response format from standardizeApi endpoint");
|
|
666
|
+
}
|
|
667
|
+
// If errorsFound is not present, default to 0 (no errors found)
|
|
668
|
+
if (!hasErrorsFound(result)) {
|
|
669
|
+
return { ...result, errorsFound: 0 };
|
|
670
|
+
}
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
616
673
|
}
|
|
@@ -2,6 +2,7 @@ export class SwaggerConfiguration {
|
|
|
2
2
|
token;
|
|
3
3
|
portalBasePath;
|
|
4
4
|
registryBasePath;
|
|
5
|
+
uiBasePath;
|
|
5
6
|
userManagementBasePath;
|
|
6
7
|
headers;
|
|
7
8
|
constructor(param) {
|
|
@@ -9,7 +10,8 @@ export class SwaggerConfiguration {
|
|
|
9
10
|
this.portalBasePath =
|
|
10
11
|
param.portalBasePath || "https://api.portal.swaggerhub.com/v1";
|
|
11
12
|
this.registryBasePath =
|
|
12
|
-
param.registryBasePath || "https://api.swaggerhub.com";
|
|
13
|
+
param.registryBasePath || "https://api.swaggerhub.com"; // Default for registry API
|
|
14
|
+
this.uiBasePath = param.uiBasePath || "https://app.swaggerhub.com"; // Default for UI
|
|
13
15
|
this.userManagementBasePath =
|
|
14
16
|
param.userManagementBasePath ||
|
|
15
17
|
"https://api.swaggerhub.com/user-management/v1";
|
|
@@ -89,7 +89,8 @@ export const CreateTableOfContentsArgsSchema = z.object({
|
|
|
89
89
|
.describe("Content configuration for the table of contents item")
|
|
90
90
|
.refine((content) => {
|
|
91
91
|
if (content?.type === "apiUrl") {
|
|
92
|
-
return content.url?.endsWith("/swagger.json")
|
|
92
|
+
return (content.url?.endsWith("/swagger.json") ||
|
|
93
|
+
content.url?.endsWith("/swagger.yaml"));
|
|
93
94
|
}
|
|
94
95
|
return true;
|
|
95
96
|
}, {
|
|
@@ -171,6 +172,11 @@ export const CreateProductArgsSchema = PortalArgsSchema.extend({
|
|
|
171
172
|
type: z
|
|
172
173
|
.string()
|
|
173
174
|
.describe("Product creation type - 'new' to create from scratch or 'copy' to duplicate an existing product"),
|
|
175
|
+
productId: z
|
|
176
|
+
.string()
|
|
177
|
+
.uuid()
|
|
178
|
+
.optional()
|
|
179
|
+
.describe("Source product UUID to copy from - required when type is 'copy', specifies which existing product to duplicate. Omit when type is 'new'"),
|
|
174
180
|
name: z
|
|
175
181
|
.string()
|
|
176
182
|
.describe("Product display name - will be shown to users in the portal navigation and product listings (3-40 characters)"),
|
|
@@ -241,11 +247,6 @@ export const UpdateDocumentArgsSchema = z.object({
|
|
|
241
247
|
.optional()
|
|
242
248
|
.describe("Content type - 'html' for HTML content or 'markdown' for Markdown content"),
|
|
243
249
|
});
|
|
244
|
-
export const DeleteDocumentArgsSchema = z.object({
|
|
245
|
-
documentId: z
|
|
246
|
-
.string()
|
|
247
|
-
.describe("Document UUID - unique identifier for the document to delete"),
|
|
248
|
-
});
|
|
249
250
|
export const DeleteTableOfContentsArgsSchema = z.object({
|
|
250
251
|
tableOfContentsId: z
|
|
251
252
|
.string()
|