|
2 | 2 | from unittest import mock
|
3 | 3 |
|
4 | 4 | import pytest
|
| 5 | +from github.GithubException import BadCredentialsException |
5 | 6 |
|
6 | 7 | from apps.github.management.commands.github_update_external_repositories import (
|
7 | 8 | Command,
|
@@ -119,12 +120,13 @@ def test_handle(self, scenario):
|
119 | 120 | mock_project = mock.Mock(spec=Project)
|
120 | 121 |
|
121 | 122 | def sync_side_effect(repo):
|
122 |
| - idx = hash(repo.name) % len(orgs) if orgs else 0 |
| 123 | + # Use deterministic mapping based on repo name |
| 124 | + idx = sum(ord(c) for c in repo.name) % len(orgs) if orgs else 0 |
123 | 125 | return (orgs[idx], mock.Mock(project=mock_project))
|
124 | 126 |
|
125 | 127 | self.mock_sync_repository.side_effect = sync_side_effect
|
126 | 128 |
|
127 |
| - with mock.patch("builtins.print"): |
| 129 | + with mock.patch("builtins.print"): # Suppress command output during testing |
128 | 130 | self.command.handle()
|
129 | 131 |
|
130 | 132 | assert self.mock_gh.get_organization.call_count == scenario.num_orgs
|
@@ -165,3 +167,57 @@ def test_sync_organization_repositories(
|
165 | 167 |
|
166 | 168 | assert len(projects) == expected_project_count
|
167 | 169 | assert mock_sync_repository.call_count == num_repos
|
| 170 | + |
| 171 | + |
| 172 | +@pytest.mark.parametrize( |
| 173 | + ("side_effects", "expected_project_count"), |
| 174 | + [ |
| 175 | + # First repo fails with error, second succeeds with project |
| 176 | + ( |
| 177 | + [ |
| 178 | + Exception("GitHub API error"), |
| 179 | + (mock.Mock(spec=Organization), mock.Mock(project=mock.Mock(spec=Project))), |
| 180 | + ], |
| 181 | + 1, |
| 182 | + ), |
| 183 | + # First repo fails with network error, second succeeds with project |
| 184 | + ( |
| 185 | + [ |
| 186 | + ConnectionError("Network timeout"), |
| 187 | + (mock.Mock(spec=Organization), mock.Mock(project=mock.Mock(spec=Project))), |
| 188 | + ], |
| 189 | + 1, |
| 190 | + ), |
| 191 | + # Credential error should return None immediately |
| 192 | + (BadCredentialsException(401), None), |
| 193 | + ], |
| 194 | +) |
| 195 | +@mock.patch("apps.github.management.commands.github_update_external_repositories.sync_repository") |
| 196 | +def test_sync_organization_repositories_error_handling( |
| 197 | + mock_sync_repository, command, mock_gh_repository, side_effects, expected_project_count |
| 198 | +): |
| 199 | + """Test repository synchronization error handling.""" |
| 200 | + mock_organization = mock.Mock(spec=Organization) |
| 201 | + mock_organization.login = "TestOrg" |
| 202 | + |
| 203 | + mock_repositories = mock.MagicMock() |
| 204 | + mock_repositories.totalCount = 2 # Try to sync 2 repositories |
| 205 | + mock_repositories.__iter__.return_value = [mock_gh_repository] * 2 |
| 206 | + |
| 207 | + mock_sync_repository.side_effect = side_effects |
| 208 | + |
| 209 | + with mock.patch("builtins.print"): # Suppress command output during testing |
| 210 | + projects = command.sync_organization_repositories(mock_organization, mock_repositories) |
| 211 | + |
| 212 | + if isinstance(side_effects, BadCredentialsException): |
| 213 | + assert projects is None |
| 214 | + assert mock_sync_repository.call_count == 1 |
| 215 | + else: |
| 216 | + assert ( |
| 217 | + len(projects) == expected_project_count |
| 218 | + ) # Should get 1 project from second successful repo |
| 219 | + assert mock_sync_repository.call_count == 2 # Should try both repos |
| 220 | + # For successful cases, verify repository was added to project |
| 221 | + if projects: |
| 222 | + assert len(projects) == 1 |
| 223 | + projects[0].repositories.add.assert_called_once() |
0 commit comments