1+ from unittest .mock import MagicMock , patch
2+
3+ import pytest
4+
15from sentry .constants import ObjectStatus
6+ from sentry .integrations .models .repository_project_path_config import RepositoryProjectPathConfig
27from sentry .integrations .services .repository .service import repository_service
38from sentry .models .repository import Repository
9+ from sentry .seer .models .project_repository import SeerProjectRepository
410from sentry .testutils .cases import TestCase
11+ from sentry .testutils .helpers .features import with_feature
512from sentry .testutils .silo import cell_silo_test
613
714
@@ -15,7 +22,8 @@ def setUp(self) -> None:
1522 )
1623 self .provider = "integrations:github"
1724
18- def test_disables_matching_active_repos (self ) -> None :
25+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
26+ def test_disables_matching_active_repos (self , mock_seer_cleanup : MagicMock ) -> None :
1927 repo1 = Repository .objects .create (
2028 organization_id = self .organization .id ,
2129 name = "getsentry/sentry" ,
@@ -111,7 +119,8 @@ def test_does_not_affect_repos_from_other_orgs(self) -> None:
111119 repo .refresh_from_db ()
112120 assert repo .status == ObjectStatus .ACTIVE
113121
114- def test_only_disables_specified_external_ids (self ) -> None :
122+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
123+ def test_only_disables_specified_external_ids (self , mock_seer_cleanup : MagicMock ) -> None :
115124 repo_to_disable = Repository .objects .create (
116125 organization_id = self .organization .id ,
117126 name = "getsentry/sentry" ,
@@ -160,3 +169,192 @@ def test_empty_external_ids_is_noop(self) -> None:
160169
161170 repo .refresh_from_db ()
162171 assert repo .status == ObjectStatus .ACTIVE
172+
173+ @with_feature ("organizations:seer-project-settings-dual-write" )
174+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
175+ def test_cleans_up_seer_preferences (self , mock_cleanup : MagicMock ) -> None :
176+ project = self .create_project (organization = self .organization )
177+ repo = Repository .objects .create (
178+ organization_id = self .organization .id ,
179+ name = "getsentry/sentry" ,
180+ external_id = "100" ,
181+ provider = self .provider ,
182+ integration_id = self .integration .id ,
183+ status = ObjectStatus .ACTIVE ,
184+ )
185+ SeerProjectRepository .objects .create (project = project , repository_id = repo .id )
186+
187+ repository_service .disable_repositories_by_external_ids (
188+ organization_id = self .organization .id ,
189+ integration_id = self .integration .id ,
190+ provider = self .provider ,
191+ external_ids = ["100" ],
192+ )
193+
194+ repo .refresh_from_db ()
195+ assert repo .status == ObjectStatus .DISABLED
196+
197+ assert not SeerProjectRepository .objects .filter (repository_id = repo .id ).exists ()
198+ mock_cleanup .apply_async .assert_called_once_with (
199+ kwargs = {
200+ "organization_id" : self .organization .id ,
201+ "repos" : [{"repo_external_id" : "100" , "repo_provider" : self .provider }],
202+ }
203+ )
204+
205+
206+ @cell_silo_test
207+ class DisableRepositoriesForIntegrationTest (TestCase ):
208+ def setUp (self ) -> None :
209+ self .integration = self .create_integration (
210+ organization = self .organization ,
211+ external_id = "1" ,
212+ provider = "github" ,
213+ )
214+ self .provider = "integrations:github"
215+
216+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
217+ def test_disables_matching_active_repos (self , mock_seer_cleanup : MagicMock ) -> None :
218+ repo = Repository .objects .create (
219+ organization_id = self .organization .id ,
220+ name = "getsentry/sentry" ,
221+ external_id = "100" ,
222+ provider = self .provider ,
223+ integration_id = self .integration .id ,
224+ status = ObjectStatus .ACTIVE ,
225+ )
226+
227+ repository_service .disable_repositories_for_integration (
228+ organization_id = self .organization .id ,
229+ integration_id = self .integration .id ,
230+ provider = self .provider ,
231+ )
232+
233+ repo .refresh_from_db ()
234+ assert repo .status == ObjectStatus .DISABLED
235+
236+ @with_feature ("organizations:seer-project-settings-dual-write" )
237+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
238+ def test_cleans_up_seer_preferences (self , mock_cleanup : MagicMock ) -> None :
239+ project = self .create_project (organization = self .organization )
240+ repo = Repository .objects .create (
241+ organization_id = self .organization .id ,
242+ name = "getsentry/sentry" ,
243+ external_id = "100" ,
244+ provider = self .provider ,
245+ integration_id = self .integration .id ,
246+ status = ObjectStatus .ACTIVE ,
247+ )
248+ SeerProjectRepository .objects .create (project = project , repository_id = repo .id )
249+
250+ repository_service .disable_repositories_for_integration (
251+ organization_id = self .organization .id ,
252+ integration_id = self .integration .id ,
253+ provider = self .provider ,
254+ )
255+
256+ repo .refresh_from_db ()
257+ assert repo .status == ObjectStatus .DISABLED
258+
259+ assert not SeerProjectRepository .objects .filter (repository_id = repo .id ).exists ()
260+ mock_cleanup .apply_async .assert_called_once_with (
261+ kwargs = {
262+ "organization_id" : self .organization .id ,
263+ "repos" : [{"repo_external_id" : "100" , "repo_provider" : self .provider }],
264+ }
265+ )
266+
267+
268+ @cell_silo_test
269+ class DisassociateOrganizationIntegrationTest (TestCase ):
270+ def setUp (self ) -> None :
271+ self .integration = self .create_integration (
272+ organization = self .organization ,
273+ external_id = "1" ,
274+ provider = "github" ,
275+ )
276+ self .provider = "integrations:github"
277+ self .org_integration = self .integration .organizationintegration_set .first ()
278+
279+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
280+ def test_disassociates_repos (self , mock_cleanup : MagicMock ) -> None :
281+ repo = Repository .objects .create (
282+ organization_id = self .organization .id ,
283+ name = "getsentry/sentry" ,
284+ external_id = "100" ,
285+ provider = self .provider ,
286+ integration_id = self .integration .id ,
287+ status = ObjectStatus .ACTIVE ,
288+ )
289+
290+ repository_service .disassociate_organization_integration (
291+ organization_id = self .organization .id ,
292+ organization_integration_id = self .org_integration .id ,
293+ integration_id = self .integration .id ,
294+ )
295+
296+ repo .refresh_from_db ()
297+ assert repo .integration_id is None
298+ mock_cleanup .apply_async .assert_called_once ()
299+
300+ @with_feature ("organizations:seer-project-settings-dual-write" )
301+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
302+ def test_cleans_up_seer_preferences (self , mock_cleanup : MagicMock ) -> None :
303+ project = self .create_project (organization = self .organization )
304+ repo = Repository .objects .create (
305+ organization_id = self .organization .id ,
306+ name = "getsentry/sentry" ,
307+ external_id = "100" ,
308+ provider = self .provider ,
309+ integration_id = self .integration .id ,
310+ status = ObjectStatus .ACTIVE ,
311+ )
312+ SeerProjectRepository .objects .create (project = project , repository_id = repo .id )
313+
314+ repository_service .disassociate_organization_integration (
315+ organization_id = self .organization .id ,
316+ organization_integration_id = self .org_integration .id ,
317+ integration_id = self .integration .id ,
318+ )
319+
320+ repo .refresh_from_db ()
321+ assert repo .integration_id is None
322+ assert not SeerProjectRepository .objects .filter (repository_id = repo .id ).exists ()
323+ mock_cleanup .apply_async .assert_called_once_with (
324+ kwargs = {
325+ "organization_id" : self .organization .id ,
326+ "repos" : [{"repo_external_id" : "100" , "repo_provider" : self .provider }],
327+ }
328+ )
329+
330+ @patch ("sentry.integrations.services.repository.impl.bulk_cleanup_seer_repository_preferences" )
331+ def test_transaction_rollback_does_not_dispatch_seer_cleanup (
332+ self , mock_cleanup : MagicMock
333+ ) -> None :
334+ repo = Repository .objects .create (
335+ organization_id = self .organization .id ,
336+ name = "getsentry/sentry" ,
337+ external_id = "100" ,
338+ provider = self .provider ,
339+ integration_id = self .integration .id ,
340+ status = ObjectStatus .ACTIVE ,
341+ )
342+
343+ with patch .object (
344+ RepositoryProjectPathConfig .objects ,
345+ "filter" ,
346+ side_effect = RuntimeError ("simulated failure" ),
347+ ):
348+ with pytest .raises (RuntimeError ):
349+ repository_service .disassociate_organization_integration (
350+ organization_id = self .organization .id ,
351+ organization_integration_id = self .org_integration .id ,
352+ integration_id = self .integration .id ,
353+ )
354+
355+ # Transaction rolled back: repo should still have its integration
356+ repo .refresh_from_db ()
357+ assert repo .integration_id == self .integration .id
358+
359+ # Task should not have been dispatched
360+ mock_cleanup .apply_async .assert_not_called ()
0 commit comments