mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
636 lines
24 KiB
Python
636 lines
24 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import asdict
|
|
from datetime import datetime, timezone
|
|
from unittest import mock
|
|
from unittest.mock import patch
|
|
from urllib.parse import quote
|
|
|
|
import pytest
|
|
import responses
|
|
from requests.exceptions import ConnectionError
|
|
|
|
from fixtures.gitlab import GET_COMMIT_RESPONSE, GitLabTestCase
|
|
from sentry.auth.exceptions import IdentityNotValid
|
|
from sentry.constants import ObjectStatus
|
|
from sentry.integrations.gitlab.blame import GitLabCommitResponse, GitLabFileBlameResponseItem
|
|
from sentry.integrations.gitlab.utils import get_rate_limit_info_from_response
|
|
from sentry.integrations.mixins.commit_context import CommitInfo, FileBlameInfo, SourceLineInfo
|
|
from sentry.integrations.request_buffer import IntegrationRequestBuffer
|
|
from sentry.models.identity import Identity
|
|
from sentry.models.integrations import Integration
|
|
from sentry.shared_integrations.exceptions import ApiError, ApiHostError, ApiRateLimitedError
|
|
from sentry.testutils.outbox import outbox_runner
|
|
from sentry.testutils.silo import control_silo_test
|
|
from sentry.utils import json
|
|
from sentry.utils.cache import cache
|
|
|
|
GITLAB_CODEOWNERS = {
|
|
"filepath": "CODEOWNERS",
|
|
"html_url": "https://gitlab.com/org/reponame/CODEOWNERS",
|
|
"raw": "docs/* @NisanthanNanthakumar @getsentry/ecosystem\n* @NisanthanNanthakumar\n",
|
|
}
|
|
|
|
|
|
class GitLabClientTest(GitLabTestCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.gitlab_client = self.installation.get_client()
|
|
self.gitlab_client.base_url = "https://example.gitlab.com/"
|
|
self.request_data = {"id": "user_id"}
|
|
self.request_url = "https://example.gitlab.com/api/v4/user"
|
|
self.refresh_url = "https://example.gitlab.com/oauth/token"
|
|
self.refresh_response = {
|
|
"access_token": "123432sfh29uhs29347",
|
|
"token_type": "bearer",
|
|
"refresh_token": "29f43sdfsk22fsj929",
|
|
"created_at": 1536798907,
|
|
"scope": "api",
|
|
}
|
|
self.repo = self.create_repo(name="Test-Org/foo", external_id=123)
|
|
self.original_identity_data = dict(self.gitlab_client.identity.data)
|
|
self.gitlab_id = 123
|
|
|
|
|
|
@control_silo_test
|
|
class GitlabRefreshAuthTest(GitLabClientTest):
|
|
get_user_should_succeed = True
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
def tearDown(self):
|
|
responses.reset()
|
|
|
|
def make_users_request(self):
|
|
return self.gitlab_client.get_user()
|
|
|
|
def add_refresh_auth(self, success=True):
|
|
responses.add(
|
|
responses.POST,
|
|
self.refresh_url,
|
|
status=200 if success else 401,
|
|
json=self.refresh_response if success else {},
|
|
)
|
|
|
|
def add_get_user_response(self, success):
|
|
responses.add(
|
|
responses.GET,
|
|
self.request_url,
|
|
json=self.request_data if success else {},
|
|
status=200 if success else 401,
|
|
)
|
|
|
|
def assert_response_call(self, call, url, status):
|
|
assert call.request.url == url
|
|
assert call.response.status_code == status
|
|
|
|
def assert_data(self, data, expected_data):
|
|
assert data["access_token"] == expected_data["access_token"]
|
|
assert data["refresh_token"] == expected_data["refresh_token"]
|
|
assert data["created_at"] == expected_data["created_at"]
|
|
|
|
def assert_request_failed_refresh(self):
|
|
responses_calls = responses.calls
|
|
assert len(responses_calls) == 2
|
|
|
|
self.assert_response_call(responses_calls[0], self.request_url, 401)
|
|
self.assert_response_call(responses_calls[1], self.refresh_url, 401)
|
|
|
|
def assert_request_with_refresh(self):
|
|
responses_calls = responses.calls
|
|
assert len(responses_calls) == 3
|
|
|
|
self.assert_response_call(responses_calls[0], self.request_url, 401)
|
|
self.assert_response_call(responses_calls[1], self.refresh_url, 200)
|
|
self.assert_response_call(responses_calls[2], self.request_url, 200)
|
|
|
|
assert json.loads(responses_calls[2].response.text) == self.request_data
|
|
|
|
def assert_identity_was_refreshed(self):
|
|
data = self.gitlab_client.identity.data
|
|
self.assert_data(data, self.refresh_response)
|
|
|
|
data = Identity.objects.get(id=self.gitlab_client.identity.id).data
|
|
self.assert_data(data, self.refresh_response)
|
|
|
|
def assert_identity_was_not_refreshed(self):
|
|
data = self.gitlab_client.identity.data
|
|
self.assert_data(data, self.original_identity_data)
|
|
|
|
data = Identity.objects.get(id=self.gitlab_client.identity.id).data
|
|
self.assert_data(data, self.original_identity_data)
|
|
|
|
@responses.activate
|
|
def test_refresh_auth_flow(self):
|
|
# Fail first then succeed
|
|
self.add_get_user_response(success=False)
|
|
self.add_get_user_response(success=True)
|
|
|
|
self.add_refresh_auth(success=True)
|
|
|
|
resp = self.make_users_request()
|
|
self.assert_request_with_refresh()
|
|
assert resp == self.request_data
|
|
self.assert_identity_was_refreshed()
|
|
|
|
@responses.activate
|
|
def test_refresh_auth_fails_gracefully(self):
|
|
self.add_get_user_response(success=False)
|
|
self.add_refresh_auth(success=False)
|
|
|
|
with pytest.raises(IdentityNotValid):
|
|
self.make_users_request()
|
|
|
|
self.assert_request_failed_refresh()
|
|
self.assert_identity_was_not_refreshed()
|
|
|
|
@responses.activate
|
|
def test_no_refresh_when_api_call_successful(self):
|
|
self.add_get_user_response(success=True)
|
|
resp = self.make_users_request()
|
|
|
|
assert len(responses.calls) == 1
|
|
call = responses.calls[0]
|
|
self.assert_response_call(call, self.request_url, 200)
|
|
assert resp == self.request_data
|
|
self.assert_identity_was_not_refreshed()
|
|
|
|
@responses.activate
|
|
def test_check_file(self):
|
|
path = "src/file.py"
|
|
ref = "537f2e94fbc489b2564ca3d6a5f0bd9afa38c3c3"
|
|
responses.add(
|
|
responses.HEAD,
|
|
f"https://example.gitlab.com/api/v4/projects/{self.gitlab_id}/repository/files/src%2Ffile.py?ref={ref}",
|
|
json={"text": 200},
|
|
)
|
|
|
|
resp = self.gitlab_client.check_file(self.repo, path, ref)
|
|
assert responses.calls[0].response.status_code == 200
|
|
assert resp.status_code == 200
|
|
|
|
@responses.activate
|
|
def test_check_no_file(self):
|
|
path = "src/file.py"
|
|
ref = "537f2e94fbc489b2564ca3d6a5f0bd9afa38c3c3"
|
|
responses.add(
|
|
responses.HEAD,
|
|
f"https://example.gitlab.com/api/v4/projects/{self.gitlab_id}/repository/files/src%2Ffile.py?ref={ref}",
|
|
status=404,
|
|
)
|
|
with pytest.raises(ApiError):
|
|
self.gitlab_client.check_file(self.repo, path, ref)
|
|
assert responses.calls[0].response.status_code == 404
|
|
|
|
@responses.activate
|
|
def test_get_stacktrace_link(self):
|
|
path = "/src/file.py"
|
|
ref = "537f2e94fbc489b2564ca3d6a5f0bd9afa38c3c3"
|
|
responses.add(
|
|
responses.HEAD,
|
|
f"https://example.gitlab.com/api/v4/projects/{self.gitlab_id}/repository/files/src%2Ffile.py?ref={ref}",
|
|
json={"text": 200},
|
|
)
|
|
|
|
source_url = self.installation.get_stacktrace_link(self.repo, path, "master", ref)
|
|
assert (
|
|
source_url
|
|
== "https://example.gitlab.com/example-repo/blob/537f2e94fbc489b2564ca3d6a5f0bd9afa38c3c3/src/file.py"
|
|
)
|
|
|
|
@mock.patch(
|
|
"sentry.integrations.gitlab.integration.GitlabIntegration.check_file",
|
|
return_value=GITLAB_CODEOWNERS["html_url"],
|
|
)
|
|
@responses.activate
|
|
def test_get_codeowner_file(self, mock_check_file):
|
|
self.config = self.create_code_mapping(
|
|
repo=self.repo,
|
|
project=self.project,
|
|
)
|
|
responses.add(
|
|
method=responses.GET,
|
|
url=f"https://example.gitlab.com/api/v4/projects/{self.gitlab_id}/repository/files/CODEOWNERS?ref=master",
|
|
body="docs/* @NisanthanNanthakumar @getsentry/ecosystem\n* @NisanthanNanthakumar\n",
|
|
)
|
|
result = self.installation.get_codeowner_file(
|
|
self.config.repository, ref=self.config.default_branch
|
|
)
|
|
|
|
assert (
|
|
responses.calls[0].request.headers["Content-Type"] == "application/raw; charset=utf-8"
|
|
)
|
|
assert responses.calls[0].request.headers["Accept"] == "application/vnd.github.raw"
|
|
assert result == GITLAB_CODEOWNERS
|
|
|
|
@responses.activate
|
|
def test_get_commit(self):
|
|
commit = "a" * 40
|
|
responses.add(
|
|
method=responses.GET,
|
|
url=f"https://example.gitlab.com/api/v4/projects/{self.gitlab_id}/repository/commits/{commit}",
|
|
json=json.loads(GET_COMMIT_RESPONSE),
|
|
)
|
|
|
|
resp = self.gitlab_client.get_commit(self.gitlab_id, commit)
|
|
assert resp == json.loads(GET_COMMIT_RESPONSE)
|
|
|
|
@responses.activate
|
|
def test_get_rate_limit_info_from_response(self):
|
|
"""
|
|
When rate limit headers present, parse them and return a GitLabRateLimitInfo object
|
|
"""
|
|
responses.add(
|
|
responses.GET,
|
|
self.request_url,
|
|
json={},
|
|
status=200,
|
|
adding_headers={
|
|
"RateLimit-Limit": "1000",
|
|
"RateLimit-Remaining": "999",
|
|
"RateLimit-Reset": "1372700873",
|
|
"RateLimit-Observed": "1",
|
|
},
|
|
)
|
|
resp = self.gitlab_client.get_user()
|
|
|
|
rate_limit_info = get_rate_limit_info_from_response(resp)
|
|
|
|
assert rate_limit_info
|
|
|
|
assert rate_limit_info.limit == 1000
|
|
assert rate_limit_info.remaining == 999
|
|
assert rate_limit_info.used == 1
|
|
assert rate_limit_info.reset == 1372700873
|
|
assert rate_limit_info.next_window() == "17:47:53"
|
|
|
|
@responses.activate
|
|
def test_get_rate_limit_info_from_response_invalid(self):
|
|
"""
|
|
When rate limit headers are not present, handle gracefully and return None
|
|
"""
|
|
responses.add(
|
|
responses.GET,
|
|
self.request_url,
|
|
json={},
|
|
status=200,
|
|
)
|
|
resp = self.gitlab_client.get_user()
|
|
|
|
rate_limit_info = get_rate_limit_info_from_response(resp)
|
|
|
|
assert not rate_limit_info
|
|
|
|
|
|
@control_silo_test
|
|
class GitLabBlameForFilesTest(GitLabClientTest):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.cache_key = "integration.gitlab.client:90c877e3983404c2ccf5756f578abd5f"
|
|
self.cache_key2 = "integration.gitlab.client:4d9a6af2411001e36cd3b66f50c1bf78"
|
|
self.cache_key3 = "integration.gitlab.client:9cae8cea1a0f2b48037a956e61b7134c"
|
|
self.file_1 = SourceLineInfo(
|
|
path="src/sentry/integrations/github/client_1.py",
|
|
lineno=10,
|
|
ref="master",
|
|
repo=self.repo,
|
|
code_mapping=None, # type: ignore
|
|
)
|
|
self.file_2 = SourceLineInfo(
|
|
path="src/sentry/integrations/github/client_1.py",
|
|
lineno=15,
|
|
ref="master",
|
|
repo=self.repo,
|
|
code_mapping=None, # type: ignore
|
|
)
|
|
self.file_3 = SourceLineInfo(
|
|
path="src/sentry/integrations/github/client_2.py",
|
|
lineno=20,
|
|
ref="master",
|
|
repo=self.repo,
|
|
code_mapping=None, # type: ignore
|
|
)
|
|
self.file_4 = SourceLineInfo(
|
|
path="src/sentry/integrations/github/client_3.py",
|
|
lineno=20,
|
|
ref="master",
|
|
repo=self.repo,
|
|
code_mapping=None, # type: ignore
|
|
)
|
|
self.blame_1 = FileBlameInfo(
|
|
**asdict(self.file_1),
|
|
commit=CommitInfo(
|
|
commitId="1",
|
|
commitMessage="test message",
|
|
committedDate=datetime(2023, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
commitAuthorEmail="[email protected]",
|
|
commitAuthorName="Marvin",
|
|
),
|
|
)
|
|
self.blame_2 = FileBlameInfo(
|
|
**asdict(self.file_2),
|
|
commit=CommitInfo(
|
|
commitId="2",
|
|
commitMessage="test message",
|
|
committedDate=datetime(2023, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
commitAuthorEmail="[email protected]",
|
|
commitAuthorName="Marvin",
|
|
),
|
|
)
|
|
self.blame_3 = FileBlameInfo(
|
|
**asdict(self.file_3),
|
|
commit=CommitInfo(
|
|
commitId="3",
|
|
commitMessage="test message",
|
|
committedDate=datetime(2023, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
commitAuthorEmail="[email protected]",
|
|
commitAuthorName="Marvin",
|
|
),
|
|
)
|
|
|
|
def set_up_success_responses(self):
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_1),
|
|
json=self.make_blame_response(id="1"),
|
|
status=200,
|
|
)
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_2),
|
|
json=self.make_blame_response(id="2"),
|
|
status=200,
|
|
)
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_3),
|
|
json=self.make_blame_response(id="3"),
|
|
status=200,
|
|
)
|
|
|
|
def make_blame_request(self, file: SourceLineInfo) -> str:
|
|
return f"https://example.gitlab.com/api/v4/projects/{self.gitlab_id}/repository/files/{quote(file.path.strip('/'), safe='')}/blame?ref={file.ref}&range[start]={file.lineno}&range[end]={file.lineno}"
|
|
|
|
def make_blame_response(self, **kwargs) -> list[GitLabFileBlameResponseItem]:
|
|
return [
|
|
GitLabFileBlameResponseItem(
|
|
lines=[],
|
|
commit=GitLabCommitResponse(
|
|
id=kwargs.get("id", "1"),
|
|
message=kwargs.get("message", "test message"),
|
|
committed_date=kwargs.get("committed_date", "2023-01-01T00:00:00.000Z"),
|
|
author_name=kwargs.get("author_name", "Marvin"),
|
|
author_email=kwargs.get("author_email", "[email protected]"),
|
|
committer_email=None,
|
|
committer_name=None,
|
|
),
|
|
)
|
|
]
|
|
|
|
@responses.activate
|
|
def test_success_single_file(self):
|
|
self.set_up_success_responses()
|
|
resp = self.gitlab_client.get_blame_for_files(files=[self.file_1], extra={})
|
|
|
|
assert resp == [self.blame_1]
|
|
|
|
@responses.activate
|
|
def test_success_single_file_cached(self):
|
|
self.set_up_success_responses()
|
|
assert cache.get(self.cache_key) is None
|
|
resp = self.gitlab_client.get_blame_for_files(files=[self.file_1], extra={})
|
|
assert resp == [self.blame_1]
|
|
assert cache.get(self.cache_key) == self.make_blame_response(id="1")
|
|
|
|
# Nothing changes if we call it again
|
|
resp = self.gitlab_client.get_blame_for_files(files=[self.file_1], extra={})
|
|
assert cache.get(self.cache_key) == self.make_blame_response(id="1")
|
|
|
|
# Calling again after the cache has been cleared should still work
|
|
cache.delete(self.cache_key)
|
|
resp = self.gitlab_client.get_blame_for_files(files=[self.file_1], extra={})
|
|
assert cache.get(self.cache_key) == self.make_blame_response(id="1")
|
|
|
|
@responses.activate
|
|
def test_success_multiple_files(self):
|
|
self.set_up_success_responses()
|
|
resp = self.gitlab_client.get_blame_for_files(
|
|
files=[self.file_1, self.file_2, self.file_3], extra={}
|
|
)
|
|
assert resp == [self.blame_1, self.blame_2, self.blame_3]
|
|
|
|
@responses.activate
|
|
def test_success_multiple_files_cached(self):
|
|
self.set_up_success_responses()
|
|
assert cache.get(self.cache_key) is None
|
|
resp = self.gitlab_client.get_blame_for_files(
|
|
files=[self.file_1, self.file_2, self.file_3], extra={}
|
|
)
|
|
|
|
assert resp == [self.blame_1, self.blame_2, self.blame_3]
|
|
assert cache.get(self.cache_key) == self.make_blame_response(id="1")
|
|
assert cache.get(self.cache_key2) == self.make_blame_response(id="2")
|
|
assert cache.get(self.cache_key3) == self.make_blame_response(id="3")
|
|
|
|
# Nothing changes if we call it again
|
|
resp = self.gitlab_client.get_blame_for_files(
|
|
files=[self.file_1, self.file_2, self.file_3], extra={}
|
|
)
|
|
assert cache.get(self.cache_key) == self.make_blame_response(id="1")
|
|
assert cache.get(self.cache_key2) == self.make_blame_response(id="2")
|
|
assert cache.get(self.cache_key3) == self.make_blame_response(id="3")
|
|
|
|
# Calling again after the cache has been cleared should still work
|
|
cache.delete(self.cache_key)
|
|
resp = self.gitlab_client.get_blame_for_files(
|
|
files=[self.file_1, self.file_2, self.file_3], extra={}
|
|
)
|
|
assert cache.get(self.cache_key) == self.make_blame_response(id="1")
|
|
assert cache.get(self.cache_key2) == self.make_blame_response(id="2")
|
|
assert cache.get(self.cache_key3) == self.make_blame_response(id="3")
|
|
|
|
assert resp != self.gitlab_client.get_blame_for_files(
|
|
files=[self.file_1, self.file_2], extra={}
|
|
)
|
|
|
|
@mock.patch(
|
|
"sentry.integrations.gitlab.blame.logger.warning",
|
|
)
|
|
@responses.activate
|
|
def test_failure_404(self, mock_logger_warning):
|
|
responses.add(
|
|
responses.GET, self.make_blame_request(self.file_1), status=404, body="No file found"
|
|
)
|
|
resp = self.gitlab_client.get_blame_for_files(files=[self.file_1], extra={})
|
|
|
|
assert resp == []
|
|
mock_logger_warning.assert_called_with(
|
|
"get_blame_for_files.api_error",
|
|
extra={
|
|
"provider": "gitlab",
|
|
"org_integration_id": self.gitlab_client.org_integration_id,
|
|
"code": 404,
|
|
"error_message": "No file found",
|
|
"repo_name": self.repo.name,
|
|
"file_path": self.file_1.path,
|
|
"branch_name": self.file_1.ref,
|
|
"file_lineno": self.file_1.lineno,
|
|
},
|
|
)
|
|
|
|
@responses.activate
|
|
def test_failure_response_type(self):
|
|
responses.add(responses.GET, self.make_blame_request(self.file_1), json={}, status=200)
|
|
|
|
with pytest.raises(ApiError):
|
|
self.gitlab_client.get_blame_for_files(files=[self.file_1], extra={})
|
|
|
|
@mock.patch(
|
|
"sentry.integrations.gitlab.blame.logger.error",
|
|
)
|
|
@responses.activate
|
|
def test_failure_approaching_rate_limit(self, mock_logger_error):
|
|
"""
|
|
If there aren't enough requests left to stay above the minimum request
|
|
limit, should raise a ApiRateLimitedError.
|
|
"""
|
|
responses.add(
|
|
responses.GET,
|
|
self.make_blame_request(self.file_1),
|
|
adding_headers={
|
|
"RateLimit-Limit": "1000",
|
|
"RateLimit-Remaining": "10",
|
|
"RateLimit-Reset": "1372700873",
|
|
"RateLimit-Observed": "900",
|
|
},
|
|
json=self.make_blame_response(id="1"),
|
|
status=200,
|
|
)
|
|
|
|
with pytest.raises(ApiRateLimitedError) as excinfo:
|
|
self.gitlab_client.get_blame_for_files(files=[self.file_1, self.file_2], extra={})
|
|
|
|
assert excinfo.value.text == "Approaching GitLab API rate limit"
|
|
mock_logger_error.assert_called_with(
|
|
"get_blame_for_files.rate_limit_too_low",
|
|
extra={
|
|
"provider": "gitlab",
|
|
"org_integration_id": self.gitlab_client.org_integration_id,
|
|
"num_files": 2,
|
|
"remaining_requests": 10,
|
|
"total_requests": 1000,
|
|
"next_window": "17:47:53",
|
|
},
|
|
)
|
|
|
|
@mock.patch(
|
|
"sentry.integrations.gitlab.blame.logger.warning",
|
|
)
|
|
@responses.activate
|
|
def test_failure_partial_expected(self, mock_logger_warning):
|
|
"""
|
|
Tests that blames are still returned when some succeed
|
|
and others fail.
|
|
"""
|
|
# First file doesn't exist, second returns successfully
|
|
responses.add(responses.GET, self.make_blame_request(self.file_1), status=404)
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_2),
|
|
json=self.make_blame_response(id="2"),
|
|
status=200,
|
|
)
|
|
resp = self.gitlab_client.get_blame_for_files(files=[self.file_1, self.file_2], extra={})
|
|
|
|
# Should return the successful response
|
|
assert resp == [self.blame_2]
|
|
|
|
# Should log the unsuccessful one
|
|
mock_logger_warning.assert_called_once()
|
|
mock_logger_warning.assert_called_with(
|
|
"get_blame_for_files.api_error",
|
|
extra={
|
|
"code": 404,
|
|
"error_message": "",
|
|
"provider": "gitlab",
|
|
"org_integration_id": self.gitlab_client.org_integration_id,
|
|
"repo_name": self.repo.name,
|
|
"file_path": self.file_1.path,
|
|
"branch_name": self.file_1.ref,
|
|
"file_lineno": self.file_1.lineno,
|
|
},
|
|
)
|
|
|
|
@responses.activate
|
|
def test_failure_partial_fatal(self):
|
|
"""
|
|
Tests that the function is aborted when a fatal response is returned
|
|
"""
|
|
# First file returns a 500
|
|
responses.add(responses.GET, self.make_blame_request(self.file_1), status=500)
|
|
|
|
with pytest.raises(ApiError):
|
|
self.gitlab_client.get_blame_for_files(files=[self.file_1, self.file_2], extra={})
|
|
|
|
@responses.activate
|
|
def test_invalid_commits(self):
|
|
"""
|
|
Tests that commits lacking required data are thrown out
|
|
"""
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_1),
|
|
json=self.make_blame_response(id=None),
|
|
status=200,
|
|
)
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_2),
|
|
json=self.make_blame_response(committed_date=None),
|
|
status=200,
|
|
)
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_3),
|
|
json=self.make_blame_response(committed_date="invalid date format"),
|
|
status=200,
|
|
)
|
|
responses.add(
|
|
responses.GET,
|
|
url=self.make_blame_request(self.file_4),
|
|
json=[{"lines": [], "commit": None}],
|
|
status=200,
|
|
)
|
|
resp = self.gitlab_client.get_blame_for_files(
|
|
files=[self.file_1, self.file_2, self.file_3, self.file_4], extra={}
|
|
)
|
|
|
|
assert resp == []
|
|
|
|
|
|
@control_silo_test
|
|
class GitLabUnhappyPathTest(GitLabClientTest):
|
|
@responses.activate
|
|
@patch.object(IntegrationRequestBuffer, "is_integration_broken", return_value=True)
|
|
def test_unreachable_host(self, mock_is_integration_broken):
|
|
|
|
integration = Integration.objects.get(id=self.integration.id)
|
|
assert integration.status == ObjectStatus.ACTIVE
|
|
|
|
path = "src/file.py"
|
|
ref = "537f2e94fbc489b2564ca3d6a5f0bd9afa38c3c3"
|
|
responses.add(
|
|
responses.HEAD,
|
|
f"https://example.gitlab.com/api/v4/projects/{self.gitlab_id}/repository/files/src%2Ffile.py?ref={ref}",
|
|
body=ConnectionError("Unable to reach host: https://example.gitlab.com"),
|
|
)
|
|
with self.feature(
|
|
{"organizations:gitlab-disable-on-broken": True}
|
|
), outbox_runner(), pytest.raises(ApiHostError):
|
|
self.gitlab_client.check_file(self.repo, path, ref)
|
|
|
|
# Assert integration is disabled.
|
|
integration = Integration.objects.get(id=self.integration.id)
|
|
assert integration.status == ObjectStatus.DISABLED
|