@zereight/mcp-gitlab 2.1.57 → 2.1.59
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.ko.md +13 -1
- package/README.md +13 -1
- package/README.zh-CN.md +13 -1
- package/build/config.js +2 -0
- package/build/index.js +388 -47
- package/build/schemas.js +138 -3
- package/build/scripts/check-skill-sync.js +5 -0
- package/build/stateless/session-id.js +6 -1
- package/build/test/stateless/session-id.test.js +26 -0
- package/build/test/test-deployment-tools.js +84 -1
- package/build/test/test-dynamic-project-scope.js +362 -0
- package/build/test/test-permission-mode.js +1 -0
- package/build/test/test-toolset-filtering.js +1 -1
- package/build/tools/registry.js +106 -2
- package/build/tools/tool-descriptions.js +2 -0
- package/build/utils/redact-sensitive.js +11 -4
- package/package.json +1 -1
package/build/schemas.js
CHANGED
|
@@ -119,6 +119,8 @@ export const GitLabPipelineJobSchema = z.object({
|
|
|
119
119
|
web_url: z.string().optional(),
|
|
120
120
|
allow_failure: z.coerce.boolean().optional(),
|
|
121
121
|
retried: z.coerce.boolean().optional(),
|
|
122
|
+
when: z.string().optional(),
|
|
123
|
+
needs: z.array(z.unknown()).optional(),
|
|
122
124
|
tag_list: z.array(z.string()).optional(),
|
|
123
125
|
runner: z
|
|
124
126
|
.object({
|
|
@@ -257,6 +259,38 @@ export const GetPipelineSchema = z.object({
|
|
|
257
259
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
258
260
|
pipeline_id: z.coerce.string().describe("The ID of the pipeline"),
|
|
259
261
|
});
|
|
262
|
+
export const GetPipelineVariablesSchema = GetPipelineSchema.merge(PaginationOptionsSchema);
|
|
263
|
+
export const UpdatePipelineMetadataSchema = GetPipelineSchema.extend({
|
|
264
|
+
name: z.string().min(1).describe("New pipeline name"),
|
|
265
|
+
});
|
|
266
|
+
export const DeletePipelineSchema = GetPipelineSchema;
|
|
267
|
+
export const PipelineReportSchema = GetPipelineSchema.merge(PaginationOptionsSchema);
|
|
268
|
+
export const PlayPipelineJobsSchema = z.object({
|
|
269
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
270
|
+
job_ids: z
|
|
271
|
+
.array(z.coerce.string())
|
|
272
|
+
.min(1)
|
|
273
|
+
.refine((jobIds) => new Set(jobIds).size === jobIds.length, "job_ids must not contain duplicates")
|
|
274
|
+
.describe("Job IDs to play, in dependency order"),
|
|
275
|
+
job_variables_attributes: z
|
|
276
|
+
.array(z.object({ key: z.string(), value: z.string() }))
|
|
277
|
+
.optional()
|
|
278
|
+
.describe("Custom job variables to use when running each job"),
|
|
279
|
+
timeout_seconds: z.coerce
|
|
280
|
+
.number()
|
|
281
|
+
.int()
|
|
282
|
+
.min(1)
|
|
283
|
+
.max(600)
|
|
284
|
+
.optional()
|
|
285
|
+
.describe("Maximum seconds to wait for each job to reach a terminal status (applied per job; total duration scales with the batch size)"),
|
|
286
|
+
poll_interval_seconds: z.coerce
|
|
287
|
+
.number()
|
|
288
|
+
.int()
|
|
289
|
+
.min(1)
|
|
290
|
+
.max(30)
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("Seconds between status polls while waiting for each job"),
|
|
293
|
+
});
|
|
260
294
|
export const GitLabMergeRequestPipelineSchema = z.object({
|
|
261
295
|
id: z.coerce.string(),
|
|
262
296
|
sha: z.string(),
|
|
@@ -380,6 +414,28 @@ export const GetCiCatalogResourceSchema = z.union([
|
|
|
380
414
|
message: "Provide exactly one of id or full_path",
|
|
381
415
|
});
|
|
382
416
|
// Deployment related schemas
|
|
417
|
+
const GitLabDeploymentApprovalUserSchema = z.object({
|
|
418
|
+
id: z.coerce.string().optional(),
|
|
419
|
+
username: z.string().optional(),
|
|
420
|
+
name: z.string().optional(),
|
|
421
|
+
state: z.string().optional(),
|
|
422
|
+
avatar_url: z.string().nullable().optional(),
|
|
423
|
+
web_url: z.string().optional(),
|
|
424
|
+
});
|
|
425
|
+
const GitLabDeploymentApprovalSchema = z.object({
|
|
426
|
+
user: GitLabDeploymentApprovalUserSchema.optional(),
|
|
427
|
+
status: z.string().optional(),
|
|
428
|
+
created_at: z.string().optional(),
|
|
429
|
+
comment: z.string().nullable().optional(),
|
|
430
|
+
});
|
|
431
|
+
const GitLabDeploymentApprovalRuleSchema = z.object({
|
|
432
|
+
user_id: z.coerce.string().nullable().optional(),
|
|
433
|
+
group_id: z.coerce.string().nullable().optional(),
|
|
434
|
+
access_level: z.coerce.number().nullable().optional(),
|
|
435
|
+
access_level_description: z.string().nullable().optional(),
|
|
436
|
+
required_approvals: z.coerce.number().optional(),
|
|
437
|
+
deployment_approvals: z.array(GitLabDeploymentApprovalSchema).optional(),
|
|
438
|
+
});
|
|
383
439
|
export const GitLabDeploymentSchema = z.object({
|
|
384
440
|
id: z.coerce.string(),
|
|
385
441
|
iid: z.coerce.string().optional(),
|
|
@@ -427,6 +483,11 @@ export const GitLabDeploymentSchema = z.object({
|
|
|
427
483
|
})
|
|
428
484
|
.optional(),
|
|
429
485
|
web_url: z.string().optional(),
|
|
486
|
+
pending_approval_count: z.coerce.number().optional(),
|
|
487
|
+
approvals: z.array(GitLabDeploymentApprovalSchema).optional(),
|
|
488
|
+
approval_summary: z
|
|
489
|
+
.object({ rules: z.array(GitLabDeploymentApprovalRuleSchema).optional() })
|
|
490
|
+
.optional(),
|
|
430
491
|
});
|
|
431
492
|
export const ListDeploymentsSchema = z
|
|
432
493
|
.object({
|
|
@@ -457,6 +518,19 @@ export const GetDeploymentSchema = z.object({
|
|
|
457
518
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
458
519
|
deployment_id: z.coerce.string().describe("The ID of the deployment"),
|
|
459
520
|
});
|
|
521
|
+
export const CreateDeploymentSchema = z.object({
|
|
522
|
+
project_id: z.coerce.string(), environment: z.string(), sha: z.string(), ref: z.string(),
|
|
523
|
+
tag: z.boolean(), status: z.enum(["running", "success", "failed", "canceled"]),
|
|
524
|
+
});
|
|
525
|
+
export const UpdateDeploymentSchema = GetDeploymentSchema.extend({
|
|
526
|
+
status: z.enum(["running", "success", "failed", "canceled"]),
|
|
527
|
+
});
|
|
528
|
+
export const DeploymentApprovalSchema = GetDeploymentSchema.extend({
|
|
529
|
+
status: z.enum(["approved", "rejected"]),
|
|
530
|
+
comment: z.string().optional(),
|
|
531
|
+
represented_as: z.string().optional(),
|
|
532
|
+
});
|
|
533
|
+
export const ListDeploymentMergeRequestsSchema = GetDeploymentSchema.merge(PaginationOptionsSchema);
|
|
460
534
|
// Environment related schemas
|
|
461
535
|
const GitLabEnvironmentLastDeploymentSchema = z.object({
|
|
462
536
|
id: z.coerce.string().optional(),
|
|
@@ -496,6 +570,31 @@ export const GetEnvironmentSchema = z.object({
|
|
|
496
570
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
497
571
|
environment_id: z.coerce.string().describe("The ID of the environment"),
|
|
498
572
|
});
|
|
573
|
+
export const UpdateEnvironmentSchema = GetEnvironmentSchema.extend({
|
|
574
|
+
external_url: z.string().url().nullable().optional(),
|
|
575
|
+
tier: z.enum(["production", "staging", "testing", "development", "other"]).optional(),
|
|
576
|
+
auto_stop_setting: z.enum(["always", "with_action"]).optional(),
|
|
577
|
+
});
|
|
578
|
+
export const StopEnvironmentSchema = GetEnvironmentSchema.extend({ force: coerceBooleanString.optional() });
|
|
579
|
+
export const StopStaleEnvironmentsSchema = z.object({ project_id: z.coerce.string(), before: z.string() });
|
|
580
|
+
export const DeleteReviewAppEnvironmentsSchema = z.object({
|
|
581
|
+
project_id: z.coerce.string(), before: z.string().optional(), limit: z.coerce.number().optional(),
|
|
582
|
+
dry_run: coerceBooleanString
|
|
583
|
+
.optional()
|
|
584
|
+
.describe("Preview the cleanup when true (default); set to false to schedule deletion one week later"),
|
|
585
|
+
});
|
|
586
|
+
export const PipelineTriggerIdSchema = z.object({ project_id: z.coerce.string(), trigger_id: z.coerce.string() });
|
|
587
|
+
export const ListPipelineTriggersSchema = z.object({ project_id: z.coerce.string() });
|
|
588
|
+
export const CreatePipelineTriggerSchema = z.object({ project_id: z.coerce.string(), description: z.string() });
|
|
589
|
+
export const UpdatePipelineTriggerSchema = PipelineTriggerIdSchema.extend({ description: z.string() });
|
|
590
|
+
export const TriggerPipelineSchema = z.object({
|
|
591
|
+
project_id: z.coerce.string(), token: z.string(), ref: z.string(),
|
|
592
|
+
variables: z.record(z.string()).optional(),
|
|
593
|
+
inputs: z
|
|
594
|
+
.record(z.unknown())
|
|
595
|
+
.optional()
|
|
596
|
+
.describe("Structured pipeline inputs; supported from GitLab 17.10 behind a feature flag and generally available from GitLab 18.1. Omit on older GitLab versions."),
|
|
597
|
+
});
|
|
499
598
|
// Schema for creating a new pipeline
|
|
500
599
|
export const CreatePipelineSchema = z.object({
|
|
501
600
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
@@ -771,15 +870,51 @@ export const PlayPipelineJobSchema = z.object({
|
|
|
771
870
|
}))
|
|
772
871
|
.optional()
|
|
773
872
|
.describe("Custom job variables to use when running the job"),
|
|
873
|
+
job_inputs: z.record(z.unknown()).optional().describe("Typed job input values"),
|
|
774
874
|
});
|
|
775
875
|
// Schema for retrying a job
|
|
776
|
-
export const RetryPipelineJobSchema = PipelineJobControlSchema
|
|
876
|
+
export const RetryPipelineJobSchema = PipelineJobControlSchema.extend({
|
|
877
|
+
job_inputs: z.record(z.unknown()).optional().describe("Typed job input values"),
|
|
878
|
+
});
|
|
777
879
|
// Schema for canceling a job
|
|
778
880
|
export const CancelPipelineJobSchema = z.object({
|
|
779
881
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
780
882
|
job_id: z.coerce.string().describe("The ID of the job"),
|
|
781
883
|
force: z.coerce.boolean().optional().describe("Force cancellation of the job"),
|
|
782
884
|
});
|
|
885
|
+
export const ErasePipelineJobSchema = PipelineJobControlSchema;
|
|
886
|
+
export const WaitForPipelineSchema = GetPipelineSchema.extend({
|
|
887
|
+
timeout_seconds: z.coerce
|
|
888
|
+
.number()
|
|
889
|
+
.int()
|
|
890
|
+
.min(1)
|
|
891
|
+
.max(600)
|
|
892
|
+
.optional()
|
|
893
|
+
.describe("Maximum seconds to wait for this pipeline to reach a terminal status"),
|
|
894
|
+
poll_interval_seconds: z.coerce
|
|
895
|
+
.number()
|
|
896
|
+
.int()
|
|
897
|
+
.min(1)
|
|
898
|
+
.max(30)
|
|
899
|
+
.optional()
|
|
900
|
+
.describe("Seconds between status polls while waiting for this pipeline"),
|
|
901
|
+
});
|
|
902
|
+
export const WaitForPipelineJobSchema = PipelineJobControlSchema.extend({
|
|
903
|
+
timeout_seconds: z.coerce
|
|
904
|
+
.number()
|
|
905
|
+
.int()
|
|
906
|
+
.min(1)
|
|
907
|
+
.max(600)
|
|
908
|
+
.optional()
|
|
909
|
+
.describe("Maximum seconds to wait for this job to reach a terminal status"),
|
|
910
|
+
poll_interval_seconds: z.coerce
|
|
911
|
+
.number()
|
|
912
|
+
.int()
|
|
913
|
+
.min(1)
|
|
914
|
+
.max(30)
|
|
915
|
+
.optional()
|
|
916
|
+
.describe("Seconds between status polls while waiting for this job"),
|
|
917
|
+
});
|
|
783
918
|
// User schemas
|
|
784
919
|
export const GitLabUserSchema = z.object({
|
|
785
920
|
username: z.string().optional(), // Changed from login to match GitLab API
|
|
@@ -1229,6 +1364,7 @@ export const GitLabCreateUpdateFileResponseSchema = z.object({
|
|
|
1229
1364
|
export const GitLabSearchResponseSchema = z.object({
|
|
1230
1365
|
count: z.coerce.number().optional(),
|
|
1231
1366
|
total_pages: z.coerce.number().optional(),
|
|
1367
|
+
next_page: z.coerce.number().optional(),
|
|
1232
1368
|
current_page: z.coerce.number().optional(),
|
|
1233
1369
|
items: z.array(GitLabRepositorySchema),
|
|
1234
1370
|
});
|
|
@@ -2359,8 +2495,7 @@ export const ListGroupMergeRequestsSchema = ListMergeRequestsSchema.omit({
|
|
|
2359
2495
|
project_id: true,
|
|
2360
2496
|
}).extend({
|
|
2361
2497
|
group_id: z.coerce.string().describe("Group ID or URL-encoded path"),
|
|
2362
|
-
non_archived:
|
|
2363
|
-
.boolean()
|
|
2498
|
+
non_archived: coerceBooleanString
|
|
2364
2499
|
.optional()
|
|
2365
2500
|
.describe("Return merge requests from non-archived projects only. Defaults to true."),
|
|
2366
2501
|
source_project_id: z.coerce
|
|
@@ -35,6 +35,9 @@ export function mintSessionId(material, input) {
|
|
|
35
35
|
h: input.header,
|
|
36
36
|
t: input.token,
|
|
37
37
|
u: input.apiUrl,
|
|
38
|
+
...(input.allowedProjectIds && input.allowedProjectIds.length > 0
|
|
39
|
+
? { p: input.allowedProjectIds }
|
|
40
|
+
: {}),
|
|
38
41
|
};
|
|
39
42
|
return seal(material, STATELESS_PURPOSES.SESSION_ID, payload);
|
|
40
43
|
}
|
|
@@ -51,7 +54,9 @@ export function openSessionId(material, sid, ttlSeconds, now) {
|
|
|
51
54
|
payload.h !== "JOB-TOKEN") ||
|
|
52
55
|
typeof payload.t !== "string" ||
|
|
53
56
|
payload.t.length === 0 ||
|
|
54
|
-
typeof payload.u !== "string"
|
|
57
|
+
typeof payload.u !== "string" ||
|
|
58
|
+
(payload.p !== undefined &&
|
|
59
|
+
(!Array.isArray(payload.p) || payload.p.some(id => typeof id !== "string")))) {
|
|
55
60
|
return null;
|
|
56
61
|
}
|
|
57
62
|
return payload;
|
|
@@ -19,6 +19,32 @@ function load(current, previous) {
|
|
|
19
19
|
return m;
|
|
20
20
|
}
|
|
21
21
|
describe("mintSessionId / openSessionId", () => {
|
|
22
|
+
test("seals and restores a narrowed project scope", () => {
|
|
23
|
+
const s = secret();
|
|
24
|
+
const a = load(s);
|
|
25
|
+
const b = load(s);
|
|
26
|
+
const sid = mintSessionId(a, {
|
|
27
|
+
header: "Private-Token",
|
|
28
|
+
token: "glpat-ABCDEFG123456789-abcdef",
|
|
29
|
+
apiUrl: "https://gitlab.example.com/api/v4",
|
|
30
|
+
allowedProjectIds: ["42", "org/theorg"],
|
|
31
|
+
});
|
|
32
|
+
const opened = openSessionId(b, sid, 3600);
|
|
33
|
+
assert.ok(opened);
|
|
34
|
+
assert.deepEqual(opened.p, ["42", "org/theorg"]);
|
|
35
|
+
});
|
|
36
|
+
test("omits the scope field when no scope was narrowed", () => {
|
|
37
|
+
const s = secret();
|
|
38
|
+
const m = load(s);
|
|
39
|
+
const sid = mintSessionId(m, {
|
|
40
|
+
header: "Private-Token",
|
|
41
|
+
token: "glpat-ABCDEFG123456789-abcdef",
|
|
42
|
+
apiUrl: "https://gitlab.example.com/api/v4",
|
|
43
|
+
});
|
|
44
|
+
const opened = openSessionId(m, sid, 3600);
|
|
45
|
+
assert.ok(opened);
|
|
46
|
+
assert.equal(opened.p, undefined);
|
|
47
|
+
});
|
|
22
48
|
test("roundtrips across pods", () => {
|
|
23
49
|
const s = secret();
|
|
24
50
|
const a = load(s);
|
|
@@ -62,7 +62,7 @@ async function callTool(toolName, args, env) {
|
|
|
62
62
|
env: {
|
|
63
63
|
...process.env,
|
|
64
64
|
...env,
|
|
65
|
-
GITLAB_READ_ONLY_MODE: "true",
|
|
65
|
+
GITLAB_READ_ONLY_MODE: env.GITLAB_READ_ONLY_MODE ?? "true",
|
|
66
66
|
USE_PIPELINE: "true",
|
|
67
67
|
},
|
|
68
68
|
});
|
|
@@ -238,8 +238,62 @@ describe("deployment and environment tools", () => {
|
|
|
238
238
|
stage: "deploy",
|
|
239
239
|
pipeline: { id: "190349", status: "success", ref: "master", sha: "abc123" },
|
|
240
240
|
},
|
|
241
|
+
pending_approval_count: 1,
|
|
242
|
+
approvals: [
|
|
243
|
+
{
|
|
244
|
+
user: {
|
|
245
|
+
id: "100",
|
|
246
|
+
username: "security-user-1",
|
|
247
|
+
name: "Security User",
|
|
248
|
+
state: "active",
|
|
249
|
+
web_url: "https://gitlab.example.com/security-user-1",
|
|
250
|
+
},
|
|
251
|
+
status: "approved",
|
|
252
|
+
created_at: "2026-02-20T16:33:00.000Z",
|
|
253
|
+
comment: "Looks good to me",
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
approval_summary: {
|
|
257
|
+
rules: [
|
|
258
|
+
{
|
|
259
|
+
group_id: "134",
|
|
260
|
+
access_level: null,
|
|
261
|
+
access_level_description: "security-group",
|
|
262
|
+
required_approvals: 2,
|
|
263
|
+
deployment_approvals: [
|
|
264
|
+
{
|
|
265
|
+
user: {
|
|
266
|
+
id: "100",
|
|
267
|
+
username: "security-user-1",
|
|
268
|
+
name: "Security User",
|
|
269
|
+
state: "active",
|
|
270
|
+
web_url: "https://gitlab.example.com/security-user-1",
|
|
271
|
+
},
|
|
272
|
+
status: "approved",
|
|
273
|
+
created_at: "2026-02-20T16:33:00.000Z",
|
|
274
|
+
comment: "Looks good to me",
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
},
|
|
241
280
|
});
|
|
242
281
|
});
|
|
282
|
+
mockGitLab.addMockHandler("post", `/projects/${TEST_PROJECT_ID}/deployments`, (_req, res) => {
|
|
283
|
+
res.status(201).json({ id: "created-deployment", status: "created" });
|
|
284
|
+
});
|
|
285
|
+
mockGitLab.addMockHandler("put", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}`, (_req, res) => {
|
|
286
|
+
res.json({ id: TEST_DEPLOYMENT_ID, status: "success" });
|
|
287
|
+
});
|
|
288
|
+
mockGitLab.addMockHandler("delete", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}`, (_req, res) => {
|
|
289
|
+
res.status(204).send();
|
|
290
|
+
});
|
|
291
|
+
mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}/merge_requests`, (_req, res) => {
|
|
292
|
+
res.json([{ iid: TEST_MERGE_REQUEST_IID, title: "Shipped change" }]);
|
|
293
|
+
});
|
|
294
|
+
mockGitLab.addMockHandler("post", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}/approval`, (_req, res) => {
|
|
295
|
+
res.json({ id: TEST_DEPLOYMENT_ID, status: "approved" });
|
|
296
|
+
});
|
|
243
297
|
mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/environments`, (req, res) => {
|
|
244
298
|
res.json([
|
|
245
299
|
{
|
|
@@ -298,6 +352,35 @@ describe("deployment and environment tools", () => {
|
|
|
298
352
|
assert.strictEqual(result.id, TEST_DEPLOYMENT_ID);
|
|
299
353
|
assert.strictEqual(result.ref, "master");
|
|
300
354
|
assert.strictEqual(result.status, "success");
|
|
355
|
+
assert.strictEqual(result.pending_approval_count, 1);
|
|
356
|
+
assert.strictEqual(result.approvals[0].status, "approved");
|
|
357
|
+
assert.strictEqual(result.approvals[0].user.username, "security-user-1");
|
|
358
|
+
assert.strictEqual(result.approval_summary.rules[0].group_id, "134");
|
|
359
|
+
assert.strictEqual(result.approval_summary.rules[0].deployment_approvals[0].comment, "Looks good to me");
|
|
360
|
+
});
|
|
361
|
+
test("supports deployment lifecycle and approval operations", async () => {
|
|
362
|
+
const env = {
|
|
363
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
364
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
365
|
+
GITLAB_READ_ONLY_MODE: "false",
|
|
366
|
+
};
|
|
367
|
+
const created = await callTool("create_deployment", {
|
|
368
|
+
project_id: TEST_PROJECT_ID,
|
|
369
|
+
environment: "stage",
|
|
370
|
+
sha: "abc123",
|
|
371
|
+
ref: "master",
|
|
372
|
+
tag: false,
|
|
373
|
+
status: "running",
|
|
374
|
+
}, env);
|
|
375
|
+
assert.strictEqual(created.id, "created-deployment");
|
|
376
|
+
const updated = await callTool("update_deployment", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID, status: "success" }, env);
|
|
377
|
+
assert.strictEqual(updated.status, "success");
|
|
378
|
+
const mergeRequests = await callTool("list_deployment_merge_requests", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID }, env);
|
|
379
|
+
assert.strictEqual(mergeRequests[0].iid, TEST_MERGE_REQUEST_IID);
|
|
380
|
+
const approval = await callTool("approve_deployment", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID, status: "approved" }, env);
|
|
381
|
+
assert.strictEqual(approval.status, "approved");
|
|
382
|
+
const deleted = await callTool("delete_deployment", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID }, env);
|
|
383
|
+
assert.deepStrictEqual(deleted, { success: true });
|
|
301
384
|
});
|
|
302
385
|
test("list_environments returns environment list", async () => {
|
|
303
386
|
const result = await callTool("list_environments", { project_id: TEST_PROJECT_ID }, {
|