11import hashlib
22import hmac
3+ from copy import deepcopy
34from unittest .mock import patch
45
56from django .urls import reverse
67
78from sentry .constants import ObjectStatus
9+ from sentry .prevent .models import PreventAIConfiguration
10+ from sentry .prevent .types .config import PREVENT_AI_CONFIG_DEFAULT
11+ from sentry .silo .base import SiloMode
812from sentry .testutils .cases import APITestCase
13+ from sentry .testutils .silo import assume_test_silo_mode
14+
15+ VALID_ORG_CONFIG = {
16+ "schema_version" : "v1" ,
17+ "org_defaults" : {
18+ "bug_prediction" : {
19+ "enabled" : True ,
20+ "sensitivity" : "medium" ,
21+ "triggers" : {"on_command_phrase" : True , "on_ready_for_review" : True },
22+ },
23+ "test_generation" : {
24+ "enabled" : False ,
25+ "triggers" : {"on_command_phrase" : True , "on_ready_for_review" : False },
26+ },
27+ "vanilla" : {
28+ "enabled" : False ,
29+ "sensitivity" : "medium" ,
30+ "triggers" : {"on_command_phrase" : True , "on_ready_for_review" : False },
31+ },
32+ },
33+ "repo_overrides" : {},
34+ }
935
1036
1137class TestPreventPrReviewResolvedConfigsEndpoint (APITestCase ):
@@ -29,14 +55,222 @@ def test_requires_auth(self):
2955 "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
3056 ["test-secret" ],
3157 )
32- def test_success_returns_default_config (self ):
58+ def test_missing_sentry_org_id_returns_400 (self ):
59+ """Test that missing sentryOrgId parameter returns 400."""
60+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
61+ auth = self ._auth_header_for_get (url , {}, "test-secret" )
62+ resp = self .client .get (url , HTTP_AUTHORIZATION = auth )
63+ assert resp .status_code == 400
64+ assert "sentryOrgId" in resp .data ["detail" ]
65+
66+ @patch (
67+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
68+ ["test-secret" ],
69+ )
70+ def test_invalid_sentry_org_id_returns_400 (self ):
71+ """Test that invalid sentryOrgId (non-integer) returns 400."""
72+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
73+ params = {"sentryOrgId" : "not-a-number" , "gitOrgName" : "test-org" , "provider" : "github" }
74+ auth = self ._auth_header_for_get (url , params , "test-secret" )
75+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
76+ assert resp .status_code == 400
77+ assert "must be a valid integer" in resp .data ["detail" ]
78+
79+ @patch (
80+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
81+ ["test-secret" ],
82+ )
83+ def test_negative_sentry_org_id_returns_400 (self ):
84+ """Test that negative sentryOrgId returns 400."""
85+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
86+ params = {"sentryOrgId" : "-123" , "gitOrgName" : "test-org" , "provider" : "github" }
87+ auth = self ._auth_header_for_get (url , params , "test-secret" )
88+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
89+ assert resp .status_code == 400
90+ assert "must be a positive integer" in resp .data ["detail" ]
91+
92+ @patch (
93+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
94+ ["test-secret" ],
95+ )
96+ def test_zero_sentry_org_id_returns_400 (self ):
97+ """Test that zero sentryOrgId returns 400."""
98+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
99+ params = {"sentryOrgId" : "0" , "gitOrgName" : "test-org" , "provider" : "github" }
100+ auth = self ._auth_header_for_get (url , params , "test-secret" )
101+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
102+ assert resp .status_code == 400
103+ assert "must be a positive integer" in resp .data ["detail" ]
104+
105+ @patch (
106+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
107+ ["test-secret" ],
108+ )
109+ def test_missing_git_org_name_returns_400 (self ):
110+ """Test that missing gitOrgName parameter returns 400."""
111+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
112+ params = {"sentryOrgId" : "123" }
113+ auth = self ._auth_header_for_get (url , params , "test-secret" )
114+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
115+ assert resp .status_code == 400
116+ assert "gitOrgName" in resp .data ["detail" ]
117+
118+ @patch (
119+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
120+ ["test-secret" ],
121+ )
122+ def test_missing_provider_returns_400 (self ):
123+ """Test that missing provider parameter returns 400."""
124+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
125+ params = {"sentryOrgId" : "123" , "gitOrgName" : "test-org" }
126+ auth = self ._auth_header_for_get (url , params , "test-secret" )
127+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
128+ assert resp .status_code == 400
129+ assert "provider" in resp .data ["detail" ]
130+
131+ @patch (
132+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
133+ ["test-secret" ],
134+ )
135+ def test_returns_default_when_no_config (self ):
136+ """Test that default config is returned when no configuration exists."""
137+ org = self .create_organization ()
138+ git_org_name = "test-github-org"
139+
140+ with assume_test_silo_mode (SiloMode .CONTROL ):
141+ self .create_integration (
142+ organization = org ,
143+ provider = "github" ,
144+ name = git_org_name ,
145+ external_id = f"github:{ git_org_name } " ,
146+ status = ObjectStatus .ACTIVE ,
147+ )
148+
149+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
150+ params = {
151+ "sentryOrgId" : str (org .id ),
152+ "gitOrgName" : git_org_name ,
153+ "provider" : "github" ,
154+ }
155+ auth = self ._auth_header_for_get (url , params , "test-secret" )
156+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
157+ assert resp .status_code == 200
158+ assert resp .data == PREVENT_AI_CONFIG_DEFAULT
159+ assert resp .data ["organization" ] == {}
160+
161+ @patch (
162+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
163+ ["test-secret" ],
164+ )
165+ def test_returns_config_when_exists (self ):
166+ """Test that saved configuration is returned when it exists."""
33167 org = self .create_organization ()
168+ git_org_name = "test-github-org"
169+
170+ with assume_test_silo_mode (SiloMode .CONTROL ):
171+ integration = self .create_integration (
172+ organization = org ,
173+ provider = "github" ,
174+ name = git_org_name ,
175+ external_id = f"github:{ git_org_name } " ,
176+ status = ObjectStatus .ACTIVE ,
177+ )
178+
179+ PreventAIConfiguration .objects .create (
180+ organization_id = org .id ,
181+ integration_id = integration .id ,
182+ data = VALID_ORG_CONFIG ,
183+ )
184+
34185 url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
35- params = {"sentryOrgId" : str (org .id )}
186+ params = {
187+ "sentryOrgId" : str (org .id ),
188+ "gitOrgName" : git_org_name ,
189+ "provider" : "github" ,
190+ }
36191 auth = self ._auth_header_for_get (url , params , "test-secret" )
37192 resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
38193 assert resp .status_code == 200
39- assert resp .data == {}
194+ assert resp .data ["organization" ][git_org_name ] == VALID_ORG_CONFIG
195+
196+ @patch (
197+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
198+ ["test-secret" ],
199+ )
200+ def test_returns_404_when_integration_not_found (self ):
201+ """Test that 404 is returned when GitHub integration doesn't exist."""
202+ org = self .create_organization ()
203+
204+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
205+ params = {
206+ "sentryOrgId" : str (org .id ),
207+ "gitOrgName" : "nonexistent-org" ,
208+ "provider" : "github" ,
209+ }
210+ auth = self ._auth_header_for_get (url , params , "test-secret" )
211+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
212+ assert resp .status_code == 404
213+ assert resp .data ["detail" ] == "GitHub integration not found"
214+
215+ @patch (
216+ "sentry.overwatch.endpoints.overwatch_rpc.settings.OVERWATCH_RPC_SHARED_SECRET" ,
217+ ["test-secret" ],
218+ )
219+ def test_config_with_repo_overrides (self ):
220+ """Test that configuration with repo overrides is properly retrieved."""
221+ org = self .create_organization ()
222+ git_org_name = "test-github-org"
223+
224+ with assume_test_silo_mode (SiloMode .CONTROL ):
225+ integration = self .create_integration (
226+ organization = org ,
227+ provider = "github" ,
228+ name = git_org_name ,
229+ external_id = f"github:{ git_org_name } " ,
230+ status = ObjectStatus .ACTIVE ,
231+ )
232+
233+ config_with_overrides = deepcopy (VALID_ORG_CONFIG )
234+ config_with_overrides ["repo_overrides" ] = {
235+ "my-repo" : {
236+ "bug_prediction" : {
237+ "enabled" : True ,
238+ "sensitivity" : "high" ,
239+ "triggers" : {"on_command_phrase" : True , "on_ready_for_review" : False },
240+ },
241+ "test_generation" : {
242+ "enabled" : True ,
243+ "triggers" : {"on_command_phrase" : True , "on_ready_for_review" : True },
244+ },
245+ "vanilla" : {
246+ "enabled" : False ,
247+ "sensitivity" : "low" ,
248+ "triggers" : {"on_command_phrase" : False , "on_ready_for_review" : False },
249+ },
250+ }
251+ }
252+
253+ PreventAIConfiguration .objects .create (
254+ organization_id = org .id ,
255+ integration_id = integration .id ,
256+ data = config_with_overrides ,
257+ )
258+
259+ url = reverse ("sentry-api-0-prevent-pr-review-configs-resolved" )
260+ params = {
261+ "sentryOrgId" : str (org .id ),
262+ "gitOrgName" : git_org_name ,
263+ "provider" : "github" ,
264+ }
265+ auth = self ._auth_header_for_get (url , params , "test-secret" )
266+ resp = self .client .get (url , params , HTTP_AUTHORIZATION = auth )
267+ assert resp .status_code == 200
268+ assert (
269+ resp .data ["organization" ][git_org_name ]["repo_overrides" ]["my-repo" ]["bug_prediction" ][
270+ "sensitivity"
271+ ]
272+ == "high"
273+ )
40274
41275
42276class TestPreventPrReviewSentryOrgEndpoint (APITestCase ):
0 commit comments