15
15
16
16
from warehouse .observations .models import ObservationKind
17
17
from warehouse .observations .tasks import (
18
- execute_observation_report ,
18
+ evaluate_project_for_quarantine ,
19
+ react_to_observation_created ,
19
20
report_observation_to_helpscout ,
20
21
)
22
+ from warehouse .packaging .models import LifecycleStatus
21
23
22
24
from ...common .db .accounts import UserFactory
23
- from ...common .db .packaging import ProjectFactory , RoleFactory
25
+ from ...common .db .packaging import ProjectFactory , ReleaseFactory , RoleFactory
24
26
25
27
26
28
def test_execute_observation_report (app_config ):
@@ -29,9 +31,9 @@ def test_execute_observation_report(app_config):
29
31
observation = pretend .stub (id = pretend .stub ())
30
32
session = pretend .stub (info = {"warehouse.observations.new" : {observation }})
31
33
32
- execute_observation_report (app_config , session )
34
+ react_to_observation_created (app_config , session )
33
35
34
- assert _delay .calls == [pretend .call (observation .id )]
36
+ assert _delay .calls == [pretend .call (observation .id ), pretend . call ( observation . id ) ]
35
37
36
38
37
39
@pytest .mark .parametrize (
@@ -75,3 +77,162 @@ def test_report_observation_to_helpscout(
75
77
76
78
# If it's not supposed to report, then we shouldn't have called the service
77
79
assert bool (hs_svc_spy .calls ) == reports
80
+
81
+
82
+ class TestAutoQuarantineProject :
83
+ def test_non_malware_observation_does_not_quarantine (self , db_request ):
84
+ dummy_task = pretend .stub (name = "dummy_task" )
85
+ user = UserFactory .create ()
86
+ db_request .user = user
87
+ project = ProjectFactory .create ()
88
+
89
+ observation = project .record_observation (
90
+ request = db_request ,
91
+ kind = ObservationKind .IsDependencyConfusion ,
92
+ summary = "Project Observation" ,
93
+ payload = {},
94
+ actor = user ,
95
+ )
96
+ # Need to flush the session to ensure the Observation has an ID
97
+ db_request .db .flush ()
98
+
99
+ evaluate_project_for_quarantine (dummy_task , db_request , observation .id )
100
+
101
+ assert project .lifecycle_status != LifecycleStatus .QuarantineEnter
102
+ assert db_request .log .info .calls == [
103
+ pretend .call ("ObservationKind is not IsMalware. Not quarantining." )
104
+ ]
105
+
106
+ def test_already_quarantined_project_does_not_do_anything (self , db_request ):
107
+ dummy_task = pretend .stub (name = "dummy_task" )
108
+ user = UserFactory .create ()
109
+ db_request .user = user
110
+ project = ProjectFactory .create (
111
+ lifecycle_status = LifecycleStatus .QuarantineEnter
112
+ )
113
+
114
+ observation = project .record_observation (
115
+ request = db_request ,
116
+ kind = ObservationKind .IsMalware ,
117
+ summary = "Project Observation" ,
118
+ payload = {},
119
+ actor = user ,
120
+ )
121
+ # Need to flush the session to ensure the Observation has an ID
122
+ db_request .db .flush ()
123
+
124
+ evaluate_project_for_quarantine (dummy_task , db_request , observation .id )
125
+
126
+ assert project .lifecycle_status == LifecycleStatus .QuarantineEnter
127
+ assert db_request .log .info .calls == [
128
+ pretend .call ("Project is already quarantined. No change needed." )
129
+ ]
130
+
131
+ def test_not_enough_observers_does_not_quarantine (self , db_request ):
132
+ dummy_task = pretend .stub (name = "dummy_task" )
133
+ user = UserFactory .create ()
134
+ db_request .user = user
135
+ project = ProjectFactory .create ()
136
+
137
+ observation = project .record_observation (
138
+ request = db_request ,
139
+ kind = ObservationKind .IsMalware ,
140
+ summary = "Project Observation" ,
141
+ payload = {},
142
+ actor = user ,
143
+ )
144
+ # Need to flush the session to ensure the Observation has an ID
145
+ db_request .db .flush ()
146
+
147
+ evaluate_project_for_quarantine (dummy_task , db_request , observation .id )
148
+
149
+ assert project .lifecycle_status != LifecycleStatus .QuarantineEnter
150
+ assert db_request .log .info .calls == [
151
+ pretend .call ("Project has fewer than 2 observers. Not quarantining." )
152
+ ]
153
+
154
+ def test_no_observer_observers_does_not_quarantine (self , db_request ):
155
+ dummy_task = pretend .stub (name = "dummy_task" )
156
+ user = UserFactory .create ()
157
+ db_request .user = user
158
+ project = ProjectFactory .create ()
159
+
160
+ another_user = UserFactory .create ()
161
+
162
+ # Record 2 observations, but neither are from an observer
163
+ project .record_observation (
164
+ request = db_request ,
165
+ kind = ObservationKind .IsMalware ,
166
+ summary = "Project Observation" ,
167
+ payload = {},
168
+ actor = user ,
169
+ )
170
+ observation = project .record_observation (
171
+ request = db_request ,
172
+ kind = ObservationKind .IsMalware ,
173
+ summary = "Project Observation" ,
174
+ payload = {},
175
+ actor = another_user ,
176
+ )
177
+ # Need to flush the session to ensure the Observations has an ID
178
+ db_request .db .flush ()
179
+
180
+ evaluate_project_for_quarantine (dummy_task , db_request , observation .id )
181
+
182
+ assert project .lifecycle_status != LifecycleStatus .QuarantineEnter
183
+ assert db_request .log .info .calls == [
184
+ pretend .call (
185
+ "Project has no `User.is_observer` Observers. Not quarantining."
186
+ )
187
+ ]
188
+
189
+ def test_quarantines_project (self , db_request , notification_service , monkeypatch ):
190
+ """
191
+ Satisfies criteria for auto-quarantine:
192
+ - 2 observations
193
+ - from different observers
194
+ - one of which is an Observer
195
+ """
196
+ dummy_task = pretend .stub (name = "dummy_task" )
197
+ user = UserFactory .create (is_observer = True )
198
+ project = ProjectFactory .create ()
199
+ # Needs a release to be able to quarantine
200
+ ReleaseFactory .create (project = project )
201
+
202
+ another_user = UserFactory .create ()
203
+
204
+ db_request .route_url = pretend .call_recorder (
205
+ lambda * args , ** kw : "/project/spam/"
206
+ )
207
+ db_request .user = user
208
+
209
+ # Record 2 observations, one from an observer
210
+ project .record_observation (
211
+ request = db_request ,
212
+ kind = ObservationKind .IsMalware ,
213
+ summary = "Project Observation" ,
214
+ payload = {},
215
+ actor = user ,
216
+ )
217
+ observation = project .record_observation (
218
+ request = db_request ,
219
+ kind = ObservationKind .IsMalware ,
220
+ summary = "Project Observation" ,
221
+ payload = {},
222
+ actor = another_user ,
223
+ )
224
+ # Need to flush the session to ensure the Observation has an ID
225
+ db_request .db .flush ()
226
+
227
+ ns_svc_spy = pretend .call_recorder (lambda * args , ** kwargs : None )
228
+ monkeypatch .setattr (notification_service , "send_notification" , ns_svc_spy )
229
+
230
+ evaluate_project_for_quarantine (dummy_task , db_request , observation .id )
231
+
232
+ assert len (ns_svc_spy .calls ) == 1
233
+ assert project .lifecycle_status == LifecycleStatus .QuarantineEnter
234
+ assert db_request .log .info .calls == [
235
+ pretend .call (
236
+ "Auto-quarantining project due to multiple malware observations."
237
+ ),
238
+ ]
0 commit comments