Merge lp:~cjwatson/launchpad/snap-channels-job into lp:launchpad
- snap-channels-job
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18143 |
Proposed branch: | lp:~cjwatson/launchpad/snap-channels-job |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/snap-channels-store-client |
Diff against target: |
358 lines (+196/-9) 5 files modified
lib/lp/snappy/emailtemplates/snapbuild-manualreview.txt (+5/-0) lib/lp/snappy/emailtemplates/snapbuild-releasefailed.txt (+7/-0) lib/lp/snappy/mail/snapbuild.py (+33/-2) lib/lp/snappy/model/snapbuildjob.py (+30/-5) lib/lp/snappy/tests/test_snapbuildjob.py (+121/-2) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/snap-channels-job |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Thomi Richards (community) | Approve | ||
Launchpad code reviewers | Pending | ||
Review via email: mp+298810@code.launchpad.net |
Commit message
Automatically release snap packages that have store_channels set.
Description of the change
Automatically release snap packages that have store_channels set.
To post a comment you must log in.
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'lib/lp/snappy/emailtemplates/snapbuild-manualreview.txt' | |||
2 | --- lib/lp/snappy/emailtemplates/snapbuild-manualreview.txt 1970-01-01 00:00:00 +0000 | |||
3 | +++ lib/lp/snappy/emailtemplates/snapbuild-manualreview.txt 2016-06-30 17:00:36 +0000 | |||
4 | @@ -0,0 +1,5 @@ | |||
5 | 1 | This snap package could not be released automatically because it was held | ||
6 | 2 | for manual review. Once it has been approved, you will need to release it | ||
7 | 3 | manually from here: | ||
8 | 4 | |||
9 | 5 | %(store_url)s | ||
10 | 0 | 6 | ||
11 | === added file 'lib/lp/snappy/emailtemplates/snapbuild-releasefailed.txt' | |||
12 | --- lib/lp/snappy/emailtemplates/snapbuild-releasefailed.txt 1970-01-01 00:00:00 +0000 | |||
13 | +++ lib/lp/snappy/emailtemplates/snapbuild-releasefailed.txt 2016-06-30 17:00:36 +0000 | |||
14 | @@ -0,0 +1,7 @@ | |||
15 | 1 | Launchpad asked the store to release this snap package, but it failed: | ||
16 | 2 | |||
17 | 3 | %(store_error_message)s | ||
18 | 4 | |||
19 | 5 | You can try to release it manually here: | ||
20 | 6 | |||
21 | 7 | %(store_url)s | ||
22 | 0 | 8 | ||
23 | === modified file 'lib/lp/snappy/mail/snapbuild.py' | |||
24 | --- lib/lp/snappy/mail/snapbuild.py 2016-06-21 14:51:06 +0000 | |||
25 | +++ lib/lp/snappy/mail/snapbuild.py 2016-06-30 17:00:36 +0000 | |||
26 | @@ -60,6 +60,34 @@ | |||
27 | 60 | config.canonical.noreply_from_address, | 60 | config.canonical.noreply_from_address, |
28 | 61 | "snap-build-upload-scan-failed", build) | 61 | "snap-build-upload-scan-failed", build) |
29 | 62 | 62 | ||
30 | 63 | @classmethod | ||
31 | 64 | def forManualReview(cls, build): | ||
32 | 65 | """Create a mailer for notifying about manual review. | ||
33 | 66 | |||
34 | 67 | :param build: The relevant build. | ||
35 | 68 | """ | ||
36 | 69 | requester = build.requester | ||
37 | 70 | recipients = {requester: RecipientReason.forBuildRequester(requester)} | ||
38 | 71 | return cls( | ||
39 | 72 | "%(snap_name)s held for manual review", | ||
40 | 73 | "snapbuild-manualreview.txt", recipients, | ||
41 | 74 | config.canonical.noreply_from_address, | ||
42 | 75 | "snap-build-release-manual-review", build) | ||
43 | 76 | |||
44 | 77 | @classmethod | ||
45 | 78 | def forReleaseFailure(cls, build): | ||
46 | 79 | """Create a mailer for notifying about store release failures. | ||
47 | 80 | |||
48 | 81 | :param build: The relevant build. | ||
49 | 82 | """ | ||
50 | 83 | requester = build.requester | ||
51 | 84 | recipients = {requester: RecipientReason.forBuildRequester(requester)} | ||
52 | 85 | return cls( | ||
53 | 86 | "Store release failed for %(snap_name)s", | ||
54 | 87 | "snapbuild-releasefailed.txt", recipients, | ||
55 | 88 | config.canonical.noreply_from_address, | ||
56 | 89 | "snap-build-release-failed", build) | ||
57 | 90 | |||
58 | 63 | def __init__(self, subject, template_name, recipients, from_address, | 91 | def __init__(self, subject, template_name, recipients, from_address, |
59 | 64 | notification_type, build): | 92 | notification_type, build): |
60 | 65 | super(SnapBuildMailer, self).__init__( | 93 | super(SnapBuildMailer, self).__init__( |
61 | @@ -77,10 +105,12 @@ | |||
62 | 77 | """See `BaseMailer`.""" | 105 | """See `BaseMailer`.""" |
63 | 78 | build = self.build | 106 | build = self.build |
64 | 79 | upload_job = build.store_upload_jobs.first() | 107 | upload_job = build.store_upload_jobs.first() |
66 | 80 | if upload_job is None or upload_job.error_message is None: | 108 | if upload_job is None: |
67 | 81 | error_message = "" | 109 | error_message = "" |
68 | 110 | store_url = "" | ||
69 | 82 | else: | 111 | else: |
71 | 83 | error_message = upload_job.error_message | 112 | error_message = upload_job.error_message or "" |
72 | 113 | store_url = upload_job.store_url or "" | ||
73 | 84 | params = super(SnapBuildMailer, self)._getTemplateParams( | 114 | params = super(SnapBuildMailer, self)._getTemplateParams( |
74 | 85 | email, recipient) | 115 | email, recipient) |
75 | 86 | params.update({ | 116 | params.update({ |
76 | @@ -100,6 +130,7 @@ | |||
77 | 100 | "snap_authorize_url": canonical_url( | 130 | "snap_authorize_url": canonical_url( |
78 | 101 | build.snap, view_name="+authorize"), | 131 | build.snap, view_name="+authorize"), |
79 | 102 | "store_error_message": error_message, | 132 | "store_error_message": error_message, |
80 | 133 | "store_url": store_url, | ||
81 | 103 | }) | 134 | }) |
82 | 104 | if build.duration is not None: | 135 | if build.duration is not None: |
83 | 105 | duration_formatter = DurationFormatterAPI(build.duration) | 136 | duration_formatter = DurationFormatterAPI(build.duration) |
84 | 106 | 137 | ||
85 | === modified file 'lib/lp/snappy/model/snapbuildjob.py' | |||
86 | --- lib/lp/snappy/model/snapbuildjob.py 2016-06-21 14:51:06 +0000 | |||
87 | +++ lib/lp/snappy/model/snapbuildjob.py 2016-06-30 17:00:36 +0000 | |||
88 | @@ -50,8 +50,10 @@ | |||
89 | 50 | ISnapStoreUploadJobSource, | 50 | ISnapStoreUploadJobSource, |
90 | 51 | ) | 51 | ) |
91 | 52 | from lp.snappy.interfaces.snapstoreclient import ( | 52 | from lp.snappy.interfaces.snapstoreclient import ( |
92 | 53 | BadReleaseResponse, | ||
93 | 53 | BadScanStatusResponse, | 54 | BadScanStatusResponse, |
94 | 54 | ISnapStoreClient, | 55 | ISnapStoreClient, |
95 | 56 | ReleaseFailedResponse, | ||
96 | 55 | ScanFailedResponse, | 57 | ScanFailedResponse, |
97 | 56 | UnauthorizedUploadResponse, | 58 | UnauthorizedUploadResponse, |
98 | 57 | UploadNotScannedYetResponse, | 59 | UploadNotScannedYetResponse, |
99 | @@ -157,6 +159,10 @@ | |||
100 | 157 | return oops_vars | 159 | return oops_vars |
101 | 158 | 160 | ||
102 | 159 | 161 | ||
103 | 162 | class ManualReview(Exception): | ||
104 | 163 | pass | ||
105 | 164 | |||
106 | 165 | |||
107 | 160 | @implementer(ISnapStoreUploadJob) | 166 | @implementer(ISnapStoreUploadJob) |
108 | 161 | @provider(ISnapStoreUploadJobSource) | 167 | @provider(ISnapStoreUploadJobSource) |
109 | 162 | class SnapStoreUploadJob(SnapBuildJobDerived): | 168 | class SnapStoreUploadJob(SnapBuildJobDerived): |
110 | @@ -164,7 +170,12 @@ | |||
111 | 164 | 170 | ||
112 | 165 | class_job_type = SnapBuildJobType.STORE_UPLOAD | 171 | class_job_type = SnapBuildJobType.STORE_UPLOAD |
113 | 166 | 172 | ||
115 | 167 | user_error_types = (UnauthorizedUploadResponse, ScanFailedResponse) | 173 | user_error_types = ( |
116 | 174 | UnauthorizedUploadResponse, | ||
117 | 175 | ScanFailedResponse, | ||
118 | 176 | ManualReview, | ||
119 | 177 | ReleaseFailedResponse, | ||
120 | 178 | ) | ||
121 | 168 | 179 | ||
122 | 169 | # XXX cjwatson 2016-05-04: identify transient upload failures and retry | 180 | # XXX cjwatson 2016-05-04: identify transient upload failures and retry |
123 | 170 | retry_error_types = (UploadNotScannedYetResponse,) | 181 | retry_error_types = (UploadNotScannedYetResponse,) |
124 | @@ -207,14 +218,19 @@ | |||
125 | 207 | try: | 218 | try: |
126 | 208 | if "status_url" not in self.metadata: | 219 | if "status_url" not in self.metadata: |
127 | 209 | self.metadata["status_url"] = client.upload(self.snapbuild) | 220 | self.metadata["status_url"] = client.upload(self.snapbuild) |
129 | 210 | self.store_url = client.checkStatus(self.metadata["status_url"]) | 221 | if self.store_url is None: |
130 | 222 | self.store_url, self.metadata["store_revision"] = ( | ||
131 | 223 | client.checkStatus(self.metadata["status_url"])) | ||
132 | 224 | if self.snapbuild.snap.store_channels: | ||
133 | 225 | if self.metadata["store_revision"] is None: | ||
134 | 226 | raise ManualReview( | ||
135 | 227 | "Package held for manual review on the store; " | ||
136 | 228 | "cannot release it automatically.") | ||
137 | 229 | client.release(self.snapbuild, self.metadata["store_revision"]) | ||
138 | 211 | self.error_message = None | 230 | self.error_message = None |
139 | 212 | except self.retry_error_types: | 231 | except self.retry_error_types: |
140 | 213 | raise | 232 | raise |
141 | 214 | except Exception as e: | 233 | except Exception as e: |
142 | 215 | # Abort work done so far, but make sure that we commit the error | ||
143 | 216 | # message. | ||
144 | 217 | transaction.abort() | ||
145 | 218 | self.error_message = str(e) | 234 | self.error_message = str(e) |
146 | 219 | if isinstance(e, UnauthorizedUploadResponse): | 235 | if isinstance(e, UnauthorizedUploadResponse): |
147 | 220 | mailer = SnapBuildMailer.forUnauthorizedUpload(self.snapbuild) | 236 | mailer = SnapBuildMailer.forUnauthorizedUpload(self.snapbuild) |
148 | @@ -222,5 +238,14 @@ | |||
149 | 222 | elif isinstance(e, (BadScanStatusResponse, ScanFailedResponse)): | 238 | elif isinstance(e, (BadScanStatusResponse, ScanFailedResponse)): |
150 | 223 | mailer = SnapBuildMailer.forUploadScanFailure(self.snapbuild) | 239 | mailer = SnapBuildMailer.forUploadScanFailure(self.snapbuild) |
151 | 224 | mailer.sendAll() | 240 | mailer.sendAll() |
152 | 241 | elif isinstance(e, ManualReview): | ||
153 | 242 | mailer = SnapBuildMailer.forManualReview(self.snapbuild) | ||
154 | 243 | mailer.sendAll() | ||
155 | 244 | elif isinstance(e, (BadReleaseResponse, ReleaseFailedResponse)): | ||
156 | 245 | mailer = SnapBuildMailer.forReleaseFailure(self.snapbuild) | ||
157 | 246 | mailer.sendAll() | ||
158 | 247 | # The normal job infrastructure will abort the transaction, but | ||
159 | 248 | # we want to commit instead: the only database changes we make | ||
160 | 249 | # are to this job's metadata and should be preserved. | ||
161 | 225 | transaction.commit() | 250 | transaction.commit() |
162 | 226 | raise | 251 | raise |
163 | 227 | 252 | ||
164 | === modified file 'lib/lp/snappy/tests/test_snapbuildjob.py' | |||
165 | --- lib/lp/snappy/tests/test_snapbuildjob.py 2016-06-21 14:51:06 +0000 | |||
166 | +++ lib/lp/snappy/tests/test_snapbuildjob.py 2016-06-30 17:00:36 +0000 | |||
167 | @@ -20,6 +20,7 @@ | |||
168 | 20 | ISnapStoreUploadJob, | 20 | ISnapStoreUploadJob, |
169 | 21 | ) | 21 | ) |
170 | 22 | from lp.snappy.interfaces.snapstoreclient import ( | 22 | from lp.snappy.interfaces.snapstoreclient import ( |
171 | 23 | BadReleaseResponse, | ||
172 | 23 | BadScanStatusResponse, | 24 | BadScanStatusResponse, |
173 | 24 | ISnapStoreClient, | 25 | ISnapStoreClient, |
174 | 25 | UnauthorizedUploadResponse, | 26 | UnauthorizedUploadResponse, |
175 | @@ -47,6 +48,7 @@ | |||
176 | 47 | def __init__(self): | 48 | def __init__(self): |
177 | 48 | self.upload = FakeMethod() | 49 | self.upload = FakeMethod() |
178 | 49 | self.checkStatus = FakeMethod() | 50 | self.checkStatus = FakeMethod() |
179 | 51 | self.release = FakeMethod() | ||
180 | 50 | 52 | ||
181 | 51 | 53 | ||
182 | 52 | class TestSnapBuildJob(TestCaseWithFactory): | 54 | class TestSnapBuildJob(TestCaseWithFactory): |
183 | @@ -95,12 +97,13 @@ | |||
184 | 95 | job = SnapStoreUploadJob.create(snapbuild) | 97 | job = SnapStoreUploadJob.create(snapbuild) |
185 | 96 | client = FakeSnapStoreClient() | 98 | client = FakeSnapStoreClient() |
186 | 97 | client.upload.result = self.status_url | 99 | client.upload.result = self.status_url |
188 | 98 | client.checkStatus.result = self.store_url | 100 | client.checkStatus.result = (self.store_url, 1) |
189 | 99 | self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) | 101 | self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) |
190 | 100 | with dbuser(config.ISnapStoreUploadJobSource.dbuser): | 102 | with dbuser(config.ISnapStoreUploadJobSource.dbuser): |
191 | 101 | JobRunner([job]).runAll() | 103 | JobRunner([job]).runAll() |
192 | 102 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | 104 | self.assertEqual([((snapbuild,), {})], client.upload.calls) |
193 | 103 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) | 105 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
194 | 106 | self.assertEqual([], client.release.calls) | ||
195 | 104 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | 107 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
196 | 105 | self.assertEqual(self.store_url, job.store_url) | 108 | self.assertEqual(self.store_url, job.store_url) |
197 | 106 | self.assertIsNone(job.error_message) | 109 | self.assertIsNone(job.error_message) |
198 | @@ -118,6 +121,7 @@ | |||
199 | 118 | JobRunner([job]).runAll() | 121 | JobRunner([job]).runAll() |
200 | 119 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | 122 | self.assertEqual([((snapbuild,), {})], client.upload.calls) |
201 | 120 | self.assertEqual([], client.checkStatus.calls) | 123 | self.assertEqual([], client.checkStatus.calls) |
202 | 124 | self.assertEqual([], client.release.calls) | ||
203 | 121 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | 125 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
204 | 122 | self.assertIsNone(job.store_url) | 126 | self.assertIsNone(job.store_url) |
205 | 123 | self.assertEqual("An upload failure", job.error_message) | 127 | self.assertEqual("An upload failure", job.error_message) |
206 | @@ -138,6 +142,7 @@ | |||
207 | 138 | JobRunner([job]).runAll() | 142 | JobRunner([job]).runAll() |
208 | 139 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | 143 | self.assertEqual([((snapbuild,), {})], client.upload.calls) |
209 | 140 | self.assertEqual([], client.checkStatus.calls) | 144 | self.assertEqual([], client.checkStatus.calls) |
210 | 145 | self.assertEqual([], client.release.calls) | ||
211 | 141 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | 146 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
212 | 142 | self.assertIsNone(job.store_url) | 147 | self.assertIsNone(job.store_url) |
213 | 143 | self.assertEqual("Authorization failed.", job.error_message) | 148 | self.assertEqual("Authorization failed.", job.error_message) |
214 | @@ -178,6 +183,7 @@ | |||
215 | 178 | JobRunner([job]).runAll() | 183 | JobRunner([job]).runAll() |
216 | 179 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | 184 | self.assertEqual([((snapbuild,), {})], client.upload.calls) |
217 | 180 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) | 185 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
218 | 186 | self.assertEqual([], client.release.calls) | ||
219 | 181 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | 187 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
220 | 182 | self.assertIsNone(job.store_url) | 188 | self.assertIsNone(job.store_url) |
221 | 183 | self.assertIsNone(job.error_message) | 189 | self.assertIsNone(job.error_message) |
222 | @@ -190,11 +196,12 @@ | |||
223 | 190 | client.upload.calls = [] | 196 | client.upload.calls = [] |
224 | 191 | client.checkStatus.calls = [] | 197 | client.checkStatus.calls = [] |
225 | 192 | client.checkStatus.failure = None | 198 | client.checkStatus.failure = None |
227 | 193 | client.checkStatus.result = self.store_url | 199 | client.checkStatus.result = (self.store_url, 1) |
228 | 194 | with dbuser(config.ISnapStoreUploadJobSource.dbuser): | 200 | with dbuser(config.ISnapStoreUploadJobSource.dbuser): |
229 | 195 | JobRunner([job]).runAll() | 201 | JobRunner([job]).runAll() |
230 | 196 | self.assertEqual([], client.upload.calls) | 202 | self.assertEqual([], client.upload.calls) |
231 | 197 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) | 203 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
232 | 204 | self.assertEqual([], client.release.calls) | ||
233 | 198 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | 205 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
234 | 199 | self.assertEqual(self.store_url, job.store_url) | 206 | self.assertEqual(self.store_url, job.store_url) |
235 | 200 | self.assertIsNone(job.error_message) | 207 | self.assertIsNone(job.error_message) |
236 | @@ -216,6 +223,7 @@ | |||
237 | 216 | JobRunner([job]).runAll() | 223 | JobRunner([job]).runAll() |
238 | 217 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | 224 | self.assertEqual([((snapbuild,), {})], client.upload.calls) |
239 | 218 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) | 225 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
240 | 226 | self.assertEqual([], client.release.calls) | ||
241 | 219 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | 227 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
242 | 220 | self.assertIsNone(job.store_url) | 228 | self.assertIsNone(job.store_url) |
243 | 221 | self.assertEqual("Scan failed.", job.error_message) | 229 | self.assertEqual("Scan failed.", job.error_message) |
244 | @@ -239,3 +247,114 @@ | |||
245 | 239 | self.assertEqual( | 247 | self.assertEqual( |
246 | 240 | "http://launchpad.dev/~requester/+snap/test-snap/+build/%d\n" | 248 | "http://launchpad.dev/~requester/+snap/test-snap/+build/%d\n" |
247 | 241 | "You are the requester of the build.\n" % snapbuild.id, footer) | 249 | "You are the requester of the build.\n" % snapbuild.id, footer) |
248 | 250 | |||
249 | 251 | def test_run_release(self): | ||
250 | 252 | # A run configured to automatically release the package to certain | ||
251 | 253 | # channels does so. | ||
252 | 254 | snapbuild = self.factory.makeSnapBuild( | ||
253 | 255 | store_channels=["stable", "edge"]) | ||
254 | 256 | self.assertContentEqual([], snapbuild.store_upload_jobs) | ||
255 | 257 | job = SnapStoreUploadJob.create(snapbuild) | ||
256 | 258 | client = FakeSnapStoreClient() | ||
257 | 259 | client.upload.result = self.status_url | ||
258 | 260 | client.checkStatus.result = (self.store_url, 1) | ||
259 | 261 | self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) | ||
260 | 262 | with dbuser(config.ISnapStoreUploadJobSource.dbuser): | ||
261 | 263 | JobRunner([job]).runAll() | ||
262 | 264 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | ||
263 | 265 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) | ||
264 | 266 | self.assertEqual([((snapbuild, 1), {})], client.release.calls) | ||
265 | 267 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | ||
266 | 268 | self.assertEqual(self.store_url, job.store_url) | ||
267 | 269 | self.assertIsNone(job.error_message) | ||
268 | 270 | self.assertEqual([], pop_notifications()) | ||
269 | 271 | |||
270 | 272 | def test_run_release_manual_review_notifies(self): | ||
271 | 273 | # A run configured to automatically release the package to certain | ||
272 | 274 | # channels but that encounters the manual review state on upload | ||
273 | 275 | # sends mail. | ||
274 | 276 | requester = self.factory.makePerson(name="requester") | ||
275 | 277 | snapbuild = self.factory.makeSnapBuild( | ||
276 | 278 | requester=requester, name="test-snap", owner=requester, | ||
277 | 279 | store_channels=["stable", "edge"]) | ||
278 | 280 | self.assertContentEqual([], snapbuild.store_upload_jobs) | ||
279 | 281 | job = SnapStoreUploadJob.create(snapbuild) | ||
280 | 282 | client = FakeSnapStoreClient() | ||
281 | 283 | client.upload.result = self.status_url | ||
282 | 284 | client.checkStatus.result = (self.store_url, None) | ||
283 | 285 | self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) | ||
284 | 286 | with dbuser(config.ISnapStoreUploadJobSource.dbuser): | ||
285 | 287 | JobRunner([job]).runAll() | ||
286 | 288 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | ||
287 | 289 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) | ||
288 | 290 | self.assertEqual([], client.release.calls) | ||
289 | 291 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | ||
290 | 292 | self.assertEqual(self.store_url, job.store_url) | ||
291 | 293 | self.assertEqual( | ||
292 | 294 | "Package held for manual review on the store; " | ||
293 | 295 | "cannot release it automatically.", | ||
294 | 296 | job.error_message) | ||
295 | 297 | [notification] = pop_notifications() | ||
296 | 298 | self.assertEqual( | ||
297 | 299 | config.canonical.noreply_from_address, notification["From"]) | ||
298 | 300 | self.assertEqual( | ||
299 | 301 | "Requester <%s>" % requester.preferredemail.email, | ||
300 | 302 | notification["To"]) | ||
301 | 303 | subject = notification["Subject"].replace("\n ", " ") | ||
302 | 304 | self.assertEqual("test-snap held for manual review", subject) | ||
303 | 305 | self.assertEqual( | ||
304 | 306 | "Requester", notification["X-Launchpad-Message-Rationale"]) | ||
305 | 307 | self.assertEqual( | ||
306 | 308 | requester.name, notification["X-Launchpad-Message-For"]) | ||
307 | 309 | self.assertEqual( | ||
308 | 310 | "snap-build-release-manual-review", | ||
309 | 311 | notification["X-Launchpad-Notification-Type"]) | ||
310 | 312 | body, footer = notification.get_payload(decode=True).split("\n-- \n") | ||
311 | 313 | self.assertIn(self.store_url, body) | ||
312 | 314 | self.assertEqual( | ||
313 | 315 | "http://launchpad.dev/~requester/+snap/test-snap/+build/%d\n" | ||
314 | 316 | "You are the requester of the build.\n" % snapbuild.id, footer) | ||
315 | 317 | |||
316 | 318 | def test_run_release_failure_notifies(self): | ||
317 | 319 | # A run configured to automatically release the package to certain | ||
318 | 320 | # channels but that fails to do so sends mail. | ||
319 | 321 | requester = self.factory.makePerson(name="requester") | ||
320 | 322 | snapbuild = self.factory.makeSnapBuild( | ||
321 | 323 | requester=requester, name="test-snap", owner=requester, | ||
322 | 324 | store_channels=["stable", "edge"]) | ||
323 | 325 | self.assertContentEqual([], snapbuild.store_upload_jobs) | ||
324 | 326 | job = SnapStoreUploadJob.create(snapbuild) | ||
325 | 327 | client = FakeSnapStoreClient() | ||
326 | 328 | client.upload.result = self.status_url | ||
327 | 329 | client.checkStatus.result = (self.store_url, 1) | ||
328 | 330 | client.release.failure = BadReleaseResponse("Failed to publish") | ||
329 | 331 | self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) | ||
330 | 332 | with dbuser(config.ISnapStoreUploadJobSource.dbuser): | ||
331 | 333 | JobRunner([job]).runAll() | ||
332 | 334 | self.assertEqual([((snapbuild,), {})], client.upload.calls) | ||
333 | 335 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) | ||
334 | 336 | self.assertEqual([((snapbuild, 1), {})], client.release.calls) | ||
335 | 337 | self.assertContentEqual([job], snapbuild.store_upload_jobs) | ||
336 | 338 | self.assertEqual(self.store_url, job.store_url) | ||
337 | 339 | self.assertEqual("Failed to publish", job.error_message) | ||
338 | 340 | [notification] = pop_notifications() | ||
339 | 341 | self.assertEqual( | ||
340 | 342 | config.canonical.noreply_from_address, notification["From"]) | ||
341 | 343 | self.assertEqual( | ||
342 | 344 | "Requester <%s>" % requester.preferredemail.email, | ||
343 | 345 | notification["To"]) | ||
344 | 346 | subject = notification["Subject"].replace("\n ", " ") | ||
345 | 347 | self.assertEqual("Store release failed for test-snap", subject) | ||
346 | 348 | self.assertEqual( | ||
347 | 349 | "Requester", notification["X-Launchpad-Message-Rationale"]) | ||
348 | 350 | self.assertEqual( | ||
349 | 351 | requester.name, notification["X-Launchpad-Message-For"]) | ||
350 | 352 | self.assertEqual( | ||
351 | 353 | "snap-build-release-failed", | ||
352 | 354 | notification["X-Launchpad-Notification-Type"]) | ||
353 | 355 | body, footer = notification.get_payload(decode=True).split("\n-- \n") | ||
354 | 356 | self.assertIn("Failed to publish", body) | ||
355 | 357 | self.assertIn(self.store_url, body) | ||
356 | 358 | self.assertEqual( | ||
357 | 359 | "http://launchpad.dev/~requester/+snap/test-snap/+build/%d\n" | ||
358 | 360 | "You are the requester of the build.\n" % snapbuild.id, footer) |
+1