Merge lp:~cjwatson/launchpad/snap-webhook-store-status into lp:launchpad
- snap-webhook-store-status
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18335 |
Proposed branch: | lp:~cjwatson/launchpad/snap-webhook-store-status |
Merge into: | lp:launchpad |
Diff against target: |
493 lines (+173/-28) 7 files modified
database/schema/security.cfg (+4/-2) lib/lp/snappy/configure.zcml (+5/-1) lib/lp/snappy/interfaces/snapbuildjob.py (+7/-1) lib/lp/snappy/model/snapbuildjob.py (+40/-1) lib/lp/snappy/subscribers/snapbuild.py (+13/-4) lib/lp/snappy/tests/test_snapbuild.py (+32/-0) lib/lp/snappy/tests/test_snapbuildjob.py (+72/-19) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/snap-webhook-store-status |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+320295@code.launchpad.net |
Commit message
Extend snap webhooks to include store_upload_
Description of the change
This is needed for build.snapcraft.io, so that it can increment metrics when snaps are released to edge; but in general it seems reasonable to include store upload status as well as build status in the webhook payload and trigger deliveries when it changes.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'database/schema/security.cfg' |
2 | --- database/schema/security.cfg 2016-11-23 03:42:51 +0000 |
3 | +++ database/schema/security.cfg 2017-03-20 00:43:16 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2009-2017 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | # |
9 | # Possible permissions: SELECT, INSERT, UPDATE, EXECUTE |
10 | @@ -2565,7 +2565,7 @@ |
11 | public.distroarchseries = SELECT |
12 | public.distroseries = SELECT |
13 | public.emailaddress = SELECT |
14 | -public.job = SELECT, UPDATE |
15 | +public.job = SELECT, INSERT, UPDATE |
16 | public.libraryfilealias = SELECT |
17 | public.libraryfilecontent = SELECT |
18 | public.person = SELECT |
19 | @@ -2576,3 +2576,5 @@ |
20 | public.snapfile = SELECT |
21 | public.snappyseries = SELECT |
22 | public.teammembership = SELECT |
23 | +public.webhook = SELECT |
24 | +public.webhookjob = SELECT, INSERT |
25 | |
26 | === modified file 'lib/lp/snappy/configure.zcml' |
27 | --- lib/lp/snappy/configure.zcml 2016-06-20 21:17:58 +0000 |
28 | +++ lib/lp/snappy/configure.zcml 2017-03-20 00:43:16 +0000 |
29 | @@ -1,4 +1,4 @@ |
30 | -<!-- Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
31 | +<!-- Copyright 2015-2017 Canonical Ltd. This software is licensed under the |
32 | GNU Affero General Public License version 3 (see the file LICENSE). |
33 | --> |
34 | |
35 | @@ -139,6 +139,10 @@ |
36 | <allow interface="lp.snappy.interfaces.snapbuildjob.ISnapBuildJob" /> |
37 | <allow interface="lp.snappy.interfaces.snapbuildjob.ISnapStoreUploadJob" /> |
38 | </class> |
39 | + <subscriber |
40 | + for="lp.snappy.interfaces.snapbuild.ISnapBuild |
41 | + lp.snappy.interfaces.snapbuildjob.ISnapBuildStoreUploadStatusChangedEvent" |
42 | + handler="lp.snappy.subscribers.snapbuild.snap_build_store_upload_status_changed" /> |
43 | |
44 | <webservice:register module="lp.snappy.interfaces.webservice" /> |
45 | |
46 | |
47 | === modified file 'lib/lp/snappy/interfaces/snapbuildjob.py' |
48 | --- lib/lp/snappy/interfaces/snapbuildjob.py 2016-06-21 14:51:06 +0000 |
49 | +++ lib/lp/snappy/interfaces/snapbuildjob.py 2017-03-20 00:43:16 +0000 |
50 | @@ -1,4 +1,4 @@ |
51 | -# Copyright 2016 Canonical Ltd. This software is licensed under the |
52 | +# Copyright 2016-2017 Canonical Ltd. This software is licensed under the |
53 | # GNU Affero General Public License version 3 (see the file LICENSE). |
54 | |
55 | """Snap build job interfaces.""" |
56 | @@ -8,11 +8,13 @@ |
57 | __metaclass__ = type |
58 | __all__ = [ |
59 | 'ISnapBuildJob', |
60 | + 'ISnapBuildStoreUploadStatusChangedEvent', |
61 | 'ISnapStoreUploadJob', |
62 | 'ISnapStoreUploadJobSource', |
63 | ] |
64 | |
65 | from lazr.restful.fields import Reference |
66 | +from zope.component.interfaces import IObjectEvent |
67 | from zope.interface import ( |
68 | Attribute, |
69 | Interface, |
70 | @@ -42,6 +44,10 @@ |
71 | metadata = Attribute(_("A dict of data about the job.")) |
72 | |
73 | |
74 | +class ISnapBuildStoreUploadStatusChangedEvent(IObjectEvent): |
75 | + """The store upload status of a snap package build changed.""" |
76 | + |
77 | + |
78 | class ISnapStoreUploadJob(IRunnableJob): |
79 | """A Job that uploads a snap build to the store.""" |
80 | |
81 | |
82 | === modified file 'lib/lp/snappy/model/snapbuildjob.py' |
83 | --- lib/lp/snappy/model/snapbuildjob.py 2016-10-16 22:04:39 +0000 |
84 | +++ lib/lp/snappy/model/snapbuildjob.py 2017-03-20 00:43:16 +0000 |
85 | @@ -1,4 +1,4 @@ |
86 | -# Copyright 2016 Canonical Ltd. This software is licensed under the |
87 | +# Copyright 2016-2017 Canonical Ltd. This software is licensed under the |
88 | # GNU Affero General Public License version 3 (see the file LICENSE). |
89 | |
90 | """Snap build jobs.""" |
91 | @@ -9,6 +9,7 @@ |
92 | __all__ = [ |
93 | 'SnapBuildJob', |
94 | 'SnapBuildJobType', |
95 | + 'SnapBuildStoreUploadStatusChangedEvent', |
96 | 'SnapStoreUploadJob', |
97 | ] |
98 | |
99 | @@ -26,6 +27,8 @@ |
100 | ) |
101 | import transaction |
102 | from zope.component import getUtility |
103 | +from zope.component.interfaces import ObjectEvent |
104 | +from zope.event import notify |
105 | from zope.interface import ( |
106 | implementer, |
107 | provider, |
108 | @@ -44,8 +47,10 @@ |
109 | Job, |
110 | ) |
111 | from lp.services.job.runner import BaseRunnableJob |
112 | +from lp.services.propertycache import get_property_cache |
113 | from lp.snappy.interfaces.snapbuildjob import ( |
114 | ISnapBuildJob, |
115 | + ISnapBuildStoreUploadStatusChangedEvent, |
116 | ISnapStoreUploadJob, |
117 | ISnapStoreUploadJobSource, |
118 | ) |
119 | @@ -164,6 +169,11 @@ |
120 | pass |
121 | |
122 | |
123 | +@implementer(ISnapBuildStoreUploadStatusChangedEvent) |
124 | +class SnapBuildStoreUploadStatusChangedEvent(ObjectEvent): |
125 | + """See `ISnapBuildStoreUploadStatusChangedEvent`.""" |
126 | + |
127 | + |
128 | @implementer(ISnapStoreUploadJob) |
129 | @provider(ISnapStoreUploadJobSource) |
130 | class SnapStoreUploadJob(SnapBuildJobDerived): |
131 | @@ -191,6 +201,8 @@ |
132 | snap_build_job = SnapBuildJob(snapbuild, cls.class_job_type, {}) |
133 | job = cls(snap_build_job) |
134 | job.celeryRunOnCommit() |
135 | + del get_property_cache(snapbuild).last_store_upload_job |
136 | + notify(SnapBuildStoreUploadStatusChangedEvent(snapbuild)) |
137 | return job |
138 | |
139 | @property |
140 | @@ -213,6 +225,33 @@ |
141 | """See `ISnapStoreUploadJob`.""" |
142 | self.metadata["store_url"] = url |
143 | |
144 | + # Ideally we'd just override Job._set_status or similar, but |
145 | + # lazr.delegates makes that difficult, so we use this to override all |
146 | + # the individual Job lifecycle methods instead. |
147 | + def _do_lifecycle(self, method, *args, **kwargs): |
148 | + old_store_upload_status = self.snapbuild.store_upload_status |
149 | + method(*args, **kwargs) |
150 | + if self.snapbuild.store_upload_status != old_store_upload_status: |
151 | + notify(SnapBuildStoreUploadStatusChangedEvent(self.snapbuild)) |
152 | + |
153 | + def start(self, *args, **kwargs): |
154 | + self._do_lifecycle(self.job.start, *args, **kwargs) |
155 | + |
156 | + def complete(self, *args, **kwargs): |
157 | + self._do_lifecycle(self.job.complete, *args, **kwargs) |
158 | + |
159 | + def fail(self, *args, **kwargs): |
160 | + self._do_lifecycle(self.job.fail, *args, **kwargs) |
161 | + |
162 | + def queue(self, *args, **kwargs): |
163 | + self._do_lifecycle(self.job.queue, *args, **kwargs) |
164 | + |
165 | + def suspend(self, *args, **kwargs): |
166 | + self._do_lifecycle(self.job.suspend, *args, **kwargs) |
167 | + |
168 | + def resume(self, *args, **kwargs): |
169 | + self._do_lifecycle(self.job.resume, *args, **kwargs) |
170 | + |
171 | def run(self): |
172 | """See `IRunnableJob`.""" |
173 | client = getUtility(ISnapStoreClient) |
174 | |
175 | === modified file 'lib/lp/snappy/subscribers/snapbuild.py' |
176 | --- lib/lp/snappy/subscribers/snapbuild.py 2016-07-19 16:32:46 +0000 |
177 | +++ lib/lp/snappy/subscribers/snapbuild.py 2017-03-20 00:43:16 +0000 |
178 | @@ -1,4 +1,4 @@ |
179 | -# Copyright 2016 Canonical Ltd. This software is licensed under the |
180 | +# Copyright 2016-2017 Canonical Ltd. This software is licensed under the |
181 | # GNU Affero General Public License version 3 (see the file LICENSE). |
182 | |
183 | """Event subscribers for snap builds.""" |
184 | @@ -19,18 +19,27 @@ |
185 | from lp.snappy.interfaces.snapbuildjob import ISnapStoreUploadJobSource |
186 | |
187 | |
188 | -def snap_build_status_changed(snapbuild, event): |
189 | - """Trigger events when snap package build statuses change.""" |
190 | +def _trigger_snap_build_webhook(snapbuild): |
191 | if getFeatureFlag(SNAP_WEBHOOKS_FEATURE_FLAG): |
192 | payload = { |
193 | "snap_build": canonical_url(snapbuild, force_local_path=True), |
194 | "action": "status-changed", |
195 | } |
196 | payload.update(compose_webhook_payload( |
197 | - ISnapBuild, snapbuild, ["snap", "status"])) |
198 | + ISnapBuild, snapbuild, ["snap", "status", "store_upload_status"])) |
199 | getUtility(IWebhookSet).trigger( |
200 | snapbuild.snap, "snap:build:0.1", payload) |
201 | |
202 | + |
203 | +def snap_build_status_changed(snapbuild, event): |
204 | + """Trigger events when snap package build statuses change.""" |
205 | + _trigger_snap_build_webhook(snapbuild) |
206 | + |
207 | if (snapbuild.snap.can_upload_to_store and snapbuild.snap.store_upload and |
208 | snapbuild.status == BuildStatus.FULLYBUILT): |
209 | getUtility(ISnapStoreUploadJobSource).create(snapbuild) |
210 | + |
211 | + |
212 | +def snap_build_store_upload_status_changed(snapbuild, event): |
213 | + """Trigger events when snap package build store upload statuses change.""" |
214 | + _trigger_snap_build_webhook(snapbuild) |
215 | |
216 | === modified file 'lib/lp/snappy/tests/test_snapbuild.py' |
217 | --- lib/lp/snappy/tests/test_snapbuild.py 2017-02-27 18:46:38 +0000 |
218 | +++ lib/lp/snappy/tests/test_snapbuild.py 2017-03-20 00:43:16 +0000 |
219 | @@ -231,6 +231,7 @@ |
220 | "snap": Equals( |
221 | canonical_url(self.build.snap, force_local_path=True)), |
222 | "status": Equals("Successfully built"), |
223 | + "store_upload_status": Equals("Unscheduled"), |
224 | } |
225 | delivery = hook.deliveries.one() |
226 | self.assertThat( |
227 | @@ -471,6 +472,37 @@ |
228 | self.assertEqual( |
229 | [], list(getUtility(ISnapStoreUploadJobSource).iterReady())) |
230 | |
231 | + def test_scheduleStoreUpload_triggers_webhooks(self): |
232 | + # Scheduling a store upload triggers webhooks on the corresponding |
233 | + # snap. |
234 | + self.setUpStoreUpload() |
235 | + self.build.updateStatus(BuildStatus.FULLYBUILT) |
236 | + self.factory.makeSnapFile( |
237 | + snapbuild=self.build, |
238 | + libraryfile=self.factory.makeLibraryFileAlias(db_only=True)) |
239 | + hook = self.factory.makeWebhook( |
240 | + target=self.build.snap, event_types=["snap:build:0.1"]) |
241 | + self.build.scheduleStoreUpload() |
242 | + expected_payload = { |
243 | + "snap_build": Equals( |
244 | + canonical_url(self.build, force_local_path=True)), |
245 | + "action": Equals("status-changed"), |
246 | + "snap": Equals( |
247 | + canonical_url(self.build.snap, force_local_path=True)), |
248 | + "status": Equals("Successfully built"), |
249 | + "store_upload_status": Equals("Pending"), |
250 | + } |
251 | + delivery = hook.deliveries.one() |
252 | + self.assertThat( |
253 | + delivery, MatchesStructure( |
254 | + event_type=Equals("snap:build:0.1"), |
255 | + payload=MatchesDict(expected_payload))) |
256 | + with dbuser(config.IWebhookDeliveryJobSource.dbuser): |
257 | + self.assertEqual( |
258 | + "<WebhookDeliveryJob for webhook %d on %r>" % ( |
259 | + hook.id, hook.target), |
260 | + repr(delivery)) |
261 | + |
262 | |
263 | class TestSnapBuildSet(TestCaseWithFactory): |
264 | |
265 | |
266 | === modified file 'lib/lp/snappy/tests/test_snapbuildjob.py' |
267 | --- lib/lp/snappy/tests/test_snapbuildjob.py 2017-01-04 20:52:12 +0000 |
268 | +++ lib/lp/snappy/tests/test_snapbuildjob.py 2017-03-20 00:43:16 +0000 |
269 | @@ -8,12 +8,20 @@ |
270 | __metaclass__ = type |
271 | |
272 | from fixtures import FakeLogger |
273 | +from testtools.matchers import ( |
274 | + Equals, |
275 | + MatchesDict, |
276 | + MatchesListwise, |
277 | + MatchesStructure, |
278 | + ) |
279 | from zope.interface import implementer |
280 | |
281 | +from lp.buildmaster.enums import BuildStatus |
282 | from lp.services.config import config |
283 | from lp.services.features.testing import FeatureFixture |
284 | from lp.services.job.interfaces.job import JobStatus |
285 | from lp.services.job.runner import JobRunner |
286 | +from lp.services.webapp.publisher import canonical_url |
287 | from lp.snappy.interfaces.snap import SNAP_TESTING_FLAGS |
288 | from lp.snappy.interfaces.snapbuildjob import ( |
289 | ISnapBuildJob, |
290 | @@ -92,10 +100,45 @@ |
291 | self.assertEqual( |
292 | "<SnapStoreUploadJob for %s>" % snapbuild.title, repr(job)) |
293 | |
294 | + def makeSnapBuild(self, **kwargs): |
295 | + # Make a build with a builder and a webhook. |
296 | + snapbuild = self.factory.makeSnapBuild( |
297 | + builder=self.factory.makeBuilder(), **kwargs) |
298 | + snapbuild.updateStatus(BuildStatus.FULLYBUILT) |
299 | + self.factory.makeWebhook( |
300 | + target=snapbuild.snap, event_types=["snap:build:0.1"]) |
301 | + return snapbuild |
302 | + |
303 | + def assertWebhookDeliveries(self, snapbuild, |
304 | + expected_store_upload_statuses): |
305 | + hook = snapbuild.snap.webhooks.one() |
306 | + deliveries = list(hook.deliveries) |
307 | + deliveries.reverse() |
308 | + expected_payloads = [{ |
309 | + "snap_build": Equals( |
310 | + canonical_url(snapbuild, force_local_path=True)), |
311 | + "action": Equals("status-changed"), |
312 | + "snap": Equals( |
313 | + canonical_url(snapbuild.snap, force_local_path=True)), |
314 | + "status": Equals("Successfully built"), |
315 | + "store_upload_status": Equals(expected), |
316 | + } for expected in expected_store_upload_statuses] |
317 | + matchers = [ |
318 | + MatchesStructure( |
319 | + event_type=Equals("snap:build:0.1"), |
320 | + payload=MatchesDict(expected_payload)) |
321 | + for expected_payload in expected_payloads] |
322 | + self.assertThat(deliveries, MatchesListwise(matchers)) |
323 | + with dbuser(config.IWebhookDeliveryJobSource.dbuser): |
324 | + for delivery in deliveries: |
325 | + self.assertEqual( |
326 | + "<WebhookDeliveryJob for webhook %d on %r>" % ( |
327 | + hook.id, hook.target), |
328 | + repr(delivery)) |
329 | + |
330 | def test_run(self): |
331 | # The job uploads the build to the store and records the store URL. |
332 | - snapbuild = self.factory.makeSnapBuild( |
333 | - builder=self.factory.makeBuilder()) |
334 | + snapbuild = self.makeSnapBuild() |
335 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
336 | job = SnapStoreUploadJob.create(snapbuild) |
337 | client = FakeSnapStoreClient() |
338 | @@ -111,11 +154,11 @@ |
339 | self.assertEqual(self.store_url, job.store_url) |
340 | self.assertIsNone(job.error_message) |
341 | self.assertEqual([], pop_notifications()) |
342 | + self.assertWebhookDeliveries(snapbuild, ["Pending", "Uploaded"]) |
343 | |
344 | def test_run_failed(self): |
345 | # A failed run sets the store upload status to FAILED. |
346 | - snapbuild = self.factory.makeSnapBuild( |
347 | - builder=self.factory.makeBuilder()) |
348 | + snapbuild = self.makeSnapBuild() |
349 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
350 | job = SnapStoreUploadJob.create(snapbuild) |
351 | client = FakeSnapStoreClient() |
352 | @@ -130,15 +173,16 @@ |
353 | self.assertIsNone(job.store_url) |
354 | self.assertEqual("An upload failure", job.error_message) |
355 | self.assertEqual([], pop_notifications()) |
356 | + self.assertWebhookDeliveries( |
357 | + snapbuild, ["Pending", "Failed to upload"]) |
358 | |
359 | def test_run_unauthorized_notifies(self): |
360 | # A run that gets 401 from the store sends mail. |
361 | requester = self.factory.makePerson(name="requester") |
362 | requester_team = self.factory.makeTeam( |
363 | owner=requester, name="requester-team", members=[requester]) |
364 | - snapbuild = self.factory.makeSnapBuild( |
365 | - requester=requester_team, name="test-snap", owner=requester_team, |
366 | - builder=self.factory.makeBuilder()) |
367 | + snapbuild = self.makeSnapBuild( |
368 | + requester=requester_team, name="test-snap", owner=requester_team) |
369 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
370 | job = SnapStoreUploadJob.create(snapbuild) |
371 | client = FakeSnapStoreClient() |
372 | @@ -177,6 +221,8 @@ |
373 | "http://launchpad.dev/~requester-team/+snap/test-snap/+build/%d\n" |
374 | "Your team Requester Team is the requester of the build.\n" % |
375 | snapbuild.id, footer) |
376 | + self.assertWebhookDeliveries( |
377 | + snapbuild, ["Pending", "Failed to upload"]) |
378 | |
379 | def test_run_upload_failure_notifies(self): |
380 | # A run that gets some other upload failure from the store sends |
381 | @@ -184,9 +230,8 @@ |
382 | requester = self.factory.makePerson(name="requester") |
383 | requester_team = self.factory.makeTeam( |
384 | owner=requester, name="requester-team", members=[requester]) |
385 | - snapbuild = self.factory.makeSnapBuild( |
386 | - requester=requester_team, name="test-snap", owner=requester_team, |
387 | - builder=self.factory.makeBuilder()) |
388 | + snapbuild = self.makeSnapBuild( |
389 | + requester=requester_team, name="test-snap", owner=requester_team) |
390 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
391 | job = SnapStoreUploadJob.create(snapbuild) |
392 | client = FakeSnapStoreClient() |
393 | @@ -225,13 +270,14 @@ |
394 | self.assertEqual( |
395 | "%s\nYour team Requester Team is the requester of the build.\n" % |
396 | build_url, footer) |
397 | + self.assertWebhookDeliveries( |
398 | + snapbuild, ["Pending", "Failed to upload"]) |
399 | |
400 | def test_run_scan_pending_retries(self): |
401 | # A run that finds that the store has not yet finished scanning the |
402 | # package schedules itself to be retried. |
403 | self.useFixture(FakeLogger()) |
404 | - snapbuild = self.factory.makeSnapBuild( |
405 | - builder=self.factory.makeBuilder()) |
406 | + snapbuild = self.makeSnapBuild() |
407 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
408 | job = SnapStoreUploadJob.create(snapbuild) |
409 | client = FakeSnapStoreClient() |
410 | @@ -248,6 +294,7 @@ |
411 | self.assertIsNone(job.error_message) |
412 | self.assertEqual([], pop_notifications()) |
413 | self.assertEqual(JobStatus.WAITING, job.job.status) |
414 | + self.assertWebhookDeliveries(snapbuild, ["Pending"]) |
415 | # Try again. The upload part of the job is not retried, and this |
416 | # time the scan completes. |
417 | job.lease_expires = None |
418 | @@ -266,15 +313,15 @@ |
419 | self.assertIsNone(job.error_message) |
420 | self.assertEqual([], pop_notifications()) |
421 | self.assertEqual(JobStatus.COMPLETED, job.job.status) |
422 | + self.assertWebhookDeliveries(snapbuild, ["Pending", "Uploaded"]) |
423 | |
424 | def test_run_scan_failure_notifies(self): |
425 | # A run that gets a scan failure from the store sends mail. |
426 | requester = self.factory.makePerson(name="requester") |
427 | requester_team = self.factory.makeTeam( |
428 | owner=requester, name="requester-team", members=[requester]) |
429 | - snapbuild = self.factory.makeSnapBuild( |
430 | - requester=requester_team, name="test-snap", owner=requester_team, |
431 | - builder=self.factory.makeBuilder()) |
432 | + snapbuild = self.makeSnapBuild( |
433 | + requester=requester_team, name="test-snap", owner=requester_team) |
434 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
435 | job = SnapStoreUploadJob.create(snapbuild) |
436 | client = FakeSnapStoreClient() |
437 | @@ -311,12 +358,13 @@ |
438 | "http://launchpad.dev/~requester-team/+snap/test-snap/+build/%d\n" |
439 | "Your team Requester Team is the requester of the build.\n" % |
440 | snapbuild.id, footer) |
441 | + self.assertWebhookDeliveries( |
442 | + snapbuild, ["Pending", "Failed to upload"]) |
443 | |
444 | def test_run_release(self): |
445 | # A run configured to automatically release the package to certain |
446 | # channels does so. |
447 | - snapbuild = self.factory.makeSnapBuild( |
448 | - store_channels=["stable", "edge"]) |
449 | + snapbuild = self.makeSnapBuild(store_channels=["stable", "edge"]) |
450 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
451 | job = SnapStoreUploadJob.create(snapbuild) |
452 | client = FakeSnapStoreClient() |
453 | @@ -332,6 +380,7 @@ |
454 | self.assertEqual(self.store_url, job.store_url) |
455 | self.assertIsNone(job.error_message) |
456 | self.assertEqual([], pop_notifications()) |
457 | + self.assertWebhookDeliveries(snapbuild, ["Pending", "Uploaded"]) |
458 | |
459 | def test_run_release_manual_review_notifies(self): |
460 | # A run configured to automatically release the package to certain |
461 | @@ -340,7 +389,7 @@ |
462 | requester = self.factory.makePerson(name="requester") |
463 | requester_team = self.factory.makeTeam( |
464 | owner=requester, name="requester-team", members=[requester]) |
465 | - snapbuild = self.factory.makeSnapBuild( |
466 | + snapbuild = self.makeSnapBuild( |
467 | requester=requester_team, name="test-snap", owner=requester_team, |
468 | store_channels=["stable", "edge"]) |
469 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
470 | @@ -382,6 +431,8 @@ |
471 | "http://launchpad.dev/~requester-team/+snap/test-snap/+build/%d\n" |
472 | "Your team Requester Team is the requester of the build.\n" % |
473 | snapbuild.id, footer) |
474 | + self.assertWebhookDeliveries( |
475 | + snapbuild, ["Pending", "Failed to release to channels"]) |
476 | |
477 | def test_run_release_failure_notifies(self): |
478 | # A run configured to automatically release the package to certain |
479 | @@ -389,7 +440,7 @@ |
480 | requester = self.factory.makePerson(name="requester") |
481 | requester_team = self.factory.makeTeam( |
482 | owner=requester, name="requester-team", members=[requester]) |
483 | - snapbuild = self.factory.makeSnapBuild( |
484 | + snapbuild = self.makeSnapBuild( |
485 | requester=requester_team, name="test-snap", owner=requester_team, |
486 | store_channels=["stable", "edge"]) |
487 | self.assertContentEqual([], snapbuild.store_upload_jobs) |
488 | @@ -430,3 +481,5 @@ |
489 | "http://launchpad.dev/~requester-team/+snap/test-snap/+build/%d\n" |
490 | "Your team Requester Team is the requester of the build.\n" % |
491 | snapbuild.id, footer) |
492 | + self.assertWebhookDeliveries( |
493 | + snapbuild, ["Pending", "Failed to release to channels"]) |