Skip to content

Commit 24b8362

Browse files
author
Jonathan Mucha
committed
updated json format for gitlab api call
1 parent 878b4b1 commit 24b8362

File tree

3 files changed

+103
-40
lines changed

3 files changed

+103
-40
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# UAT: GitLab Commit Status Integration
2+
3+
## Feature
4+
`--enable-commit-status` posts a commit status (`success`/`failed`) to GitLab after scan completes. Repo admins can then require `socket-security` as a status check on protected branches.
5+
6+
## Prerequisites
7+
- GitLab project with CI/CD configured
8+
- `GITLAB_TOKEN` with `api` scope (or `CI_JOB_TOKEN` with sufficient permissions)
9+
- Merge request pipeline (so `CI_MERGE_REQUEST_PROJECT_ID` is set)
10+
11+
## Test Cases
12+
13+
### 1. Pass scenario (no blocking alerts)
14+
1. Create MR with no dependency changes (or only safe ones)
15+
2. Run: `socketcli --scm gitlab --enable-commit-status`
16+
3. **Expected**: Commit status `socket-security` = `success`, description = "No blocking issues"
17+
4. Verify in GitLab: **Repository > Commits > (sha) > Pipelines** or **MR > Pipeline > External** tab
18+
19+
### 2. Fail scenario (blocking alerts)
20+
1. Create MR adding a package with known blocking alerts
21+
2. Run: `socketcli --scm gitlab --enable-commit-status`
22+
3. **Expected**: Commit status = `failed`, description = "N blocking alert(s) found"
23+
24+
### 3. Flag omitted (default off)
25+
1. Run: `socketcli --scm gitlab` (no `--enable-commit-status`)
26+
2. **Expected**: No commit status posted
27+
28+
### 4. Non-MR pipeline (push event without MR)
29+
1. Trigger pipeline on a push (no MR context)
30+
2. Run: `socketcli --scm gitlab --enable-commit-status`
31+
3. **Expected**: Commit status skipped (no `mr_project_id`), no error
32+
33+
### 5. API failure is non-fatal
34+
1. Use an invalid/revoked `GITLAB_TOKEN`
35+
2. Run: `socketcli --scm gitlab --enable-commit-status`
36+
3. **Expected**: Error logged ("Failed to set commit status: ..."), scan still completes with correct exit code
37+
38+
### 6. Non-GitLab SCM
39+
1. Run: `socketcli --scm github --enable-commit-status`
40+
2. **Expected**: Flag is accepted but commit status is not posted (GitHub not yet supported)
41+
42+
## Configuring Protected Branch Requirement
43+
1. Go to **Settings > Repository > Protected branches**
44+
2. Edit the target branch
45+
3. Under **Status checks**, add `socket-security` as a required external status check
46+
4. MRs targeting that branch will now require Socket's `success` status to merge

socketsecurity/core/scm/gitlab.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,15 @@ def add_socket_comments(
261261
self.post_comment(security_comment)
262262

263263
def set_commit_status(self, state: str, description: str, target_url: str = '') -> None:
264-
"""Post a commit status to GitLab. state should be 'success' or 'failed'."""
264+
"""Post a commit status to GitLab. state should be 'success' or 'failed'.
265+
266+
Uses requests.post with json= directly because CliClient.request sends
267+
data= (form-encoded) which GitLab's commit status endpoint rejects.
268+
"""
265269
if not self.config.mr_project_id:
266270
log.debug("No mr_project_id, skipping commit status")
267271
return
268-
path = f"projects/{self.config.mr_project_id}/statuses/{self.config.commit_sha}"
272+
url = f"{self.config.api_url}/projects/{self.config.mr_project_id}/statuses/{self.config.commit_sha}"
269273
payload = {
270274
"state": state,
271275
"name": "socket-security",
@@ -274,13 +278,12 @@ def set_commit_status(self, state: str, description: str, target_url: str = '')
274278
if target_url:
275279
payload["target_url"] = target_url
276280
try:
277-
self._request_with_fallback(
278-
path=path,
279-
payload=payload,
280-
method="POST",
281-
headers=self.config.headers,
282-
base_url=self.config.api_url
283-
)
281+
resp = requests.post(url, json=payload, headers=self.config.headers)
282+
if resp.status_code == 401:
283+
fallback = self._get_fallback_headers(self.config.headers)
284+
if fallback:
285+
resp = requests.post(url, json=payload, headers=fallback)
286+
resp.raise_for_status()
284287
log.info(f"Commit status set to '{state}' on {self.config.commit_sha[:8]}")
285288
except Exception as e:
286289
log.error(f"Failed to set commit status: {e}")

tests/unit/test_gitlab_commit_status.py

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,70 +31,84 @@ def _make_gitlab_config(**overrides):
3131
class TestSetCommitStatus:
3232
"""Test Gitlab.set_commit_status()"""
3333

34-
def test_calls_correct_api_path(self):
34+
@patch("socketsecurity.core.scm.gitlab.requests.post")
35+
def test_calls_correct_url_and_json_payload(self, mock_post):
36+
mock_post.return_value = MagicMock(status_code=200)
3537
config = _make_gitlab_config()
36-
client = MagicMock()
37-
gl = Gitlab(client=client, config=config)
38-
gl._request_with_fallback = MagicMock()
38+
gl = Gitlab(client=MagicMock(), config=config)
3939

4040
gl.set_commit_status("success", "No blocking issues", "https://app.socket.dev/report/123")
4141

42-
gl._request_with_fallback.assert_called_once_with(
43-
path="projects/99/statuses/abc123def456",
44-
payload={
42+
mock_post.assert_called_once_with(
43+
"https://gitlab.example.com/api/v4/projects/99/statuses/abc123def456",
44+
json={
4545
"state": "success",
4646
"name": "socket-security",
4747
"description": "No blocking issues",
4848
"target_url": "https://app.socket.dev/report/123",
4949
},
50-
method="POST",
5150
headers=config.headers,
52-
base_url=config.api_url,
5351
)
5452

55-
def test_failed_state_payload(self):
53+
@patch("socketsecurity.core.scm.gitlab.requests.post")
54+
def test_failed_state_payload(self, mock_post):
55+
mock_post.return_value = MagicMock(status_code=200)
5656
config = _make_gitlab_config()
57-
client = MagicMock()
58-
gl = Gitlab(client=client, config=config)
59-
gl._request_with_fallback = MagicMock()
57+
gl = Gitlab(client=MagicMock(), config=config)
6058

6159
gl.set_commit_status("failed", "3 blocking alert(s) found")
6260

63-
args = gl._request_with_fallback.call_args
64-
assert args.kwargs["payload"]["state"] == "failed"
65-
assert args.kwargs["payload"]["description"] == "3 blocking alert(s) found"
66-
assert "target_url" not in args.kwargs["payload"]
61+
payload = mock_post.call_args.kwargs["json"]
62+
assert payload["state"] == "failed"
63+
assert payload["description"] == "3 blocking alert(s) found"
64+
assert "target_url" not in payload
6765

68-
def test_skipped_when_no_mr_project_id(self):
66+
@patch("socketsecurity.core.scm.gitlab.requests.post")
67+
def test_skipped_when_no_mr_project_id(self, mock_post):
6968
config = _make_gitlab_config(mr_project_id=None)
70-
client = MagicMock()
71-
gl = Gitlab(client=client, config=config)
72-
gl._request_with_fallback = MagicMock()
69+
gl = Gitlab(client=MagicMock(), config=config)
7370

7471
gl.set_commit_status("success", "No blocking issues")
7572

76-
gl._request_with_fallback.assert_not_called()
73+
mock_post.assert_not_called()
7774

78-
def test_graceful_error_handling(self):
75+
@patch("socketsecurity.core.scm.gitlab.requests.post")
76+
def test_graceful_error_handling(self, mock_post):
77+
mock_post.side_effect = Exception("connection error")
7978
config = _make_gitlab_config()
80-
client = MagicMock()
81-
gl = Gitlab(client=client, config=config)
82-
gl._request_with_fallback = MagicMock(side_effect=Exception("API error"))
79+
gl = Gitlab(client=MagicMock(), config=config)
8380

8481
# Should not raise
8582
gl.set_commit_status("success", "No blocking issues")
8683

87-
def test_no_target_url_omitted_from_payload(self):
84+
@patch("socketsecurity.core.scm.gitlab.requests.post")
85+
def test_no_target_url_omitted_from_payload(self, mock_post):
86+
mock_post.return_value = MagicMock(status_code=200)
8887
config = _make_gitlab_config()
89-
client = MagicMock()
90-
gl = Gitlab(client=client, config=config)
91-
gl._request_with_fallback = MagicMock()
88+
gl = Gitlab(client=MagicMock(), config=config)
9289

9390
gl.set_commit_status("success", "No blocking issues", target_url="")
9491

95-
payload = gl._request_with_fallback.call_args.kwargs["payload"]
92+
payload = mock_post.call_args.kwargs["json"]
9693
assert "target_url" not in payload
9794

95+
@patch("socketsecurity.core.scm.gitlab.requests.post")
96+
def test_auth_fallback_on_401(self, mock_post):
97+
resp_401 = MagicMock(status_code=401)
98+
resp_401.raise_for_status.side_effect = Exception("401")
99+
resp_200 = MagicMock(status_code=200)
100+
mock_post.side_effect = [resp_401, resp_200]
101+
102+
config = _make_gitlab_config()
103+
gl = Gitlab(client=MagicMock(), config=config)
104+
105+
gl.set_commit_status("success", "No blocking issues")
106+
107+
assert mock_post.call_count == 2
108+
# Second call should use fallback headers (PRIVATE-TOKEN)
109+
fallback_headers = mock_post.call_args_list[1].kwargs["headers"]
110+
assert "PRIVATE-TOKEN" in fallback_headers
111+
98112

99113
class TestEnableCommitStatusCliArg:
100114
"""Test --enable-commit-status CLI argument parsing"""

0 commit comments

Comments
 (0)