Merge lp:~cjwatson/launchpad/refactor-macaroon-testing into lp:launchpad
- refactor-macaroon-testing
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18946 |
Proposed branch: | lp:~cjwatson/launchpad/refactor-macaroon-testing |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/authserver-issue-macaroon |
Diff against target: |
429 lines (+140/-37) 7 files modified
lib/lp/code/model/codeimportjob.py (+4/-2) lib/lp/code/model/tests/test_codeimportjob.py (+29/-14) lib/lp/services/macaroons/interfaces.py (+3/-1) lib/lp/services/macaroons/model.py (+24/-5) lib/lp/services/macaroons/testing.py (+40/-0) lib/lp/snappy/tests/test_snapbuild.py (+19/-8) lib/lp/soyuz/tests/test_binarypackagebuild.py (+21/-7) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/refactor-macaroon-testing |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+365859@code.launchpad.net |
Commit message
Add logging to macaroon verification, and refactor tests to check it.
Description of the change
It was all too easy for macaroon verification to fail for some reason other than the one that a test expected, and tests had no way to tell; indeed, I ran into and fixed exactly such a case while working on this branch. We now log details of verification failures, and I added a couple of test helpers to make it easy for tests to check these consistently.
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote : | # |
I thought actually returning a list of violations was a bit too confusing (because it would mean inverting the sense of boolean tests of verifyMacaroon), but passing a list as an optional argument which verifyMacaroon appends to seems not too horrible and works fine, so I've done that.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/model/codeimportjob.py' |
2 | --- lib/lp/code/model/codeimportjob.py 2019-04-24 12:50:20 +0000 |
3 | +++ lib/lp/code/model/codeimportjob.py 2019-04-25 22:19:14 +0000 |
4 | @@ -434,9 +434,11 @@ |
5 | |
6 | def checkVerificationContext(self, context): |
7 | """See `MacaroonIssuerBase`.""" |
8 | - if (not ICodeImportJob.providedBy(context) or |
9 | - context.state != CodeImportJobState.RUNNING): |
10 | + if not ICodeImportJob.providedBy(context): |
11 | raise BadMacaroonContext(context) |
12 | + if context.state != CodeImportJobState.RUNNING: |
13 | + raise BadMacaroonContext( |
14 | + context, "%r is not in the RUNNING state." % context) |
15 | return context |
16 | |
17 | def verifyPrimaryCaveat(self, caveat_value, context): |
18 | |
19 | === modified file 'lib/lp/code/model/tests/test_codeimportjob.py' |
20 | --- lib/lp/code/model/tests/test_codeimportjob.py 2019-04-25 09:34:40 +0000 |
21 | +++ lib/lp/code/model/tests/test_codeimportjob.py 2019-04-25 22:19:14 +0000 |
22 | @@ -65,6 +65,7 @@ |
23 | BadMacaroonContext, |
24 | IMacaroonIssuer, |
25 | ) |
26 | +from lp.services.macaroons.testing import MacaroonTestMixin |
27 | from lp.services.webapp import canonical_url |
28 | from lp.testing import ( |
29 | ANONYMOUS, |
30 | @@ -1271,7 +1272,7 @@ |
31 | get_feedback_messages(user_browser.contents)) |
32 | |
33 | |
34 | -class TestCodeImportJobMacaroonIssuer(TestCaseWithFactory): |
35 | +class TestCodeImportJobMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory): |
36 | """Test CodeImportJob macaroon issuing and verification.""" |
37 | |
38 | layer = DatabaseFunctionalLayer |
39 | @@ -1324,7 +1325,7 @@ |
40 | issuer = getUtility(IMacaroonIssuer, "code-import-job") |
41 | getUtility(ICodeImportJobWorkflow).startJob(job, machine) |
42 | macaroon = removeSecurityProxy(issuer).issueMacaroon(job) |
43 | - self.assertTrue(issuer.verifyMacaroon(macaroon, job)) |
44 | + self.assertMacaroonVerifies(issuer, macaroon, job) |
45 | |
46 | def test_verifyMacaroon_good_no_context(self): |
47 | machine = self.factory.makeCodeImportMachine(set_online=True) |
48 | @@ -1332,8 +1333,8 @@ |
49 | issuer = getUtility(IMacaroonIssuer, "code-import-job") |
50 | getUtility(ICodeImportJobWorkflow).startJob(job, machine) |
51 | macaroon = removeSecurityProxy(issuer).issueMacaroon(job) |
52 | - self.assertTrue( |
53 | - issuer.verifyMacaroon(macaroon, None, require_context=False)) |
54 | + self.assertMacaroonVerifies( |
55 | + issuer, macaroon, job, require_context=False) |
56 | |
57 | def test_verifyMacaroon_no_context_but_require_context(self): |
58 | machine = self.factory.makeCodeImportMachine(set_online=True) |
59 | @@ -1341,7 +1342,9 @@ |
60 | issuer = getUtility(IMacaroonIssuer, "code-import-job") |
61 | getUtility(ICodeImportJobWorkflow).startJob(job, machine) |
62 | macaroon = removeSecurityProxy(issuer).issueMacaroon(job) |
63 | - self.assertFalse(issuer.verifyMacaroon(macaroon, None)) |
64 | + self.assertMacaroonDoesNotVerify( |
65 | + ["Expected macaroon verification context but got None."], |
66 | + issuer, macaroon, None) |
67 | |
68 | def test_verifyMacaroon_wrong_location(self): |
69 | machine = self.factory.makeCodeImportMachine(set_online=True) |
70 | @@ -1351,9 +1354,12 @@ |
71 | macaroon = Macaroon( |
72 | location="another-location", |
73 | key=removeSecurityProxy(issuer)._root_secret) |
74 | - self.assertFalse(issuer.verifyMacaroon(macaroon, job)) |
75 | - self.assertFalse( |
76 | - issuer.verifyMacaroon(macaroon, None, require_context=False)) |
77 | + self.assertMacaroonDoesNotVerify( |
78 | + ["Macaroon has unknown location 'another-location'."], |
79 | + issuer, macaroon, job) |
80 | + self.assertMacaroonDoesNotVerify( |
81 | + ["Macaroon has unknown location 'another-location'."], |
82 | + issuer, macaroon, job, require_context=False) |
83 | |
84 | def test_verifyMacaroon_wrong_key(self): |
85 | machine = self.factory.makeCodeImportMachine(set_online=True) |
86 | @@ -1362,19 +1368,28 @@ |
87 | getUtility(ICodeImportJobWorkflow).startJob(job, machine) |
88 | macaroon = Macaroon( |
89 | location=config.vhost.mainsite.hostname, key="another-secret") |
90 | - self.assertFalse(issuer.verifyMacaroon(macaroon, job)) |
91 | - self.assertFalse( |
92 | - issuer.verifyMacaroon(macaroon, None, require_context=False)) |
93 | + self.assertMacaroonDoesNotVerify( |
94 | + ["Signatures do not match"], issuer, macaroon, job) |
95 | + self.assertMacaroonDoesNotVerify( |
96 | + ["Signatures do not match"], |
97 | + issuer, macaroon, job, require_context=False) |
98 | |
99 | def test_verifyMacaroon_not_running(self): |
100 | job = self.makeJob() |
101 | issuer = getUtility(IMacaroonIssuer, "code-import-job") |
102 | macaroon = removeSecurityProxy(issuer).issueMacaroon(job) |
103 | - self.assertFalse(issuer.verifyMacaroon(macaroon, job)) |
104 | + self.assertMacaroonDoesNotVerify( |
105 | + ["%r is not in the RUNNING state." % job], |
106 | + issuer, macaroon, job) |
107 | |
108 | def test_verifyMacaroon_wrong_job(self): |
109 | + machine = self.factory.makeCodeImportMachine(set_online=True) |
110 | job = self.makeJob() |
111 | - other_job = self.factory.makeCodeImportJob(code_import=job.code_import) |
112 | + other_job = self.makeJob() |
113 | issuer = getUtility(IMacaroonIssuer, "code-import-job") |
114 | + getUtility(ICodeImportJobWorkflow).startJob(job, machine) |
115 | macaroon = removeSecurityProxy(issuer).issueMacaroon(other_job) |
116 | - self.assertFalse(issuer.verifyMacaroon(macaroon, job)) |
117 | + self.assertMacaroonDoesNotVerify( |
118 | + ["Caveat check for 'lp.code-import-job %s' failed." % |
119 | + other_job.id], |
120 | + issuer, macaroon, job) |
121 | |
122 | === modified file 'lib/lp/services/macaroons/interfaces.py' |
123 | --- lib/lp/services/macaroons/interfaces.py 2019-04-25 09:55:35 +0000 |
124 | +++ lib/lp/services/macaroons/interfaces.py 2019-04-25 22:19:14 +0000 |
125 | @@ -31,7 +31,7 @@ |
126 | issuable_via_authserver = Bool( |
127 | "Does this issuer allow issuing macaroons via the authserver?") |
128 | |
129 | - def verifyMacaroon(macaroon, context, require_context=True): |
130 | + def verifyMacaroon(macaroon, context, require_context=True, errors=None): |
131 | """Verify that `macaroon` is valid for `context`. |
132 | |
133 | :param macaroon: A `Macaroon`. |
134 | @@ -41,6 +41,8 @@ |
135 | verify that the macaroon could be valid for some context. Use |
136 | this in the authentication part of an |
137 | authentication/authorisation API. |
138 | + :param errors: If non-None, any verification error messages will be |
139 | + appended to this list. |
140 | :return: True if `macaroon` is valid for `context`, otherwise False. |
141 | """ |
142 | |
143 | |
144 | === modified file 'lib/lp/services/macaroons/model.py' |
145 | --- lib/lp/services/macaroons/model.py 2019-04-24 16:46:31 +0000 |
146 | +++ lib/lp/services/macaroons/model.py 2019-04-25 22:19:14 +0000 |
147 | @@ -95,22 +95,33 @@ |
148 | """ |
149 | raise NotImplementedError |
150 | |
151 | - def verifyMacaroon(self, macaroon, context, require_context=True): |
152 | + def verifyMacaroon(self, macaroon, context, require_context=True, |
153 | + errors=None): |
154 | """See `IMacaroonIssuer`.""" |
155 | if macaroon.location != config.vhost.mainsite.hostname: |
156 | + if errors is not None: |
157 | + errors.append( |
158 | + "Macaroon has unknown location '%s'." % macaroon.location) |
159 | return False |
160 | if require_context and context is None: |
161 | + if errors is not None: |
162 | + errors.append( |
163 | + "Expected macaroon verification context but got None.") |
164 | return False |
165 | if context is not None: |
166 | try: |
167 | context = self.checkVerificationContext(context) |
168 | - except BadMacaroonContext: |
169 | + except BadMacaroonContext as e: |
170 | + if errors is not None: |
171 | + errors.append(str(e)) |
172 | return False |
173 | |
174 | def verify(caveat): |
175 | try: |
176 | caveat_name, caveat_value = caveat.split(" ", 1) |
177 | except ValueError: |
178 | + if errors is not None: |
179 | + errors.append("Cannot parse caveat '%s'." % caveat) |
180 | return False |
181 | if caveat_name == self._primary_caveat_name: |
182 | checker = self.verifyPrimaryCaveat |
183 | @@ -118,8 +129,14 @@ |
184 | # XXX cjwatson 2019-04-09: For now we just fail closed if |
185 | # there are any other caveats, which is good enough for |
186 | # internal use. |
187 | - return False |
188 | - return checker(caveat_value, context) |
189 | + if errors is not None: |
190 | + errors.append("Unhandled caveat name '%s'." % caveat_name) |
191 | + return False |
192 | + if not checker(caveat_value, context): |
193 | + if errors is not None: |
194 | + errors.append("Caveat check for '%s' failed." % caveat) |
195 | + return False |
196 | + return True |
197 | |
198 | try: |
199 | verifier = Verifier() |
200 | @@ -130,5 +147,7 @@ |
201 | # but most of them are too broad to reasonably catch so we let them |
202 | # turn into OOPSes for now. Revisit this once |
203 | # https://github.com/ecordell/pymacaroons/issues/51 is fixed. |
204 | - except MacaroonVerificationFailedException: |
205 | + except MacaroonVerificationFailedException as e: |
206 | + if errors is not None and not errors: |
207 | + errors.append(str(e)) |
208 | return False |
209 | |
210 | === added file 'lib/lp/services/macaroons/testing.py' |
211 | --- lib/lp/services/macaroons/testing.py 1970-01-01 00:00:00 +0000 |
212 | +++ lib/lp/services/macaroons/testing.py 2019-04-25 22:19:14 +0000 |
213 | @@ -0,0 +1,40 @@ |
214 | +# Copyright 2019 Canonical Ltd. This software is licensed under the |
215 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
216 | + |
217 | +"""Macaroon testing helpers.""" |
218 | + |
219 | +from __future__ import absolute_import, print_function, unicode_literals |
220 | + |
221 | +__metaclass__ = type |
222 | +__all__ = [ |
223 | + 'find_caveats_by_name', |
224 | + 'MacaroonTestMixin', |
225 | + ] |
226 | + |
227 | +from testtools.content import text_content |
228 | + |
229 | + |
230 | +def find_caveats_by_name(macaroon, caveat_name): |
231 | + return [ |
232 | + caveat for caveat in macaroon.caveats |
233 | + if caveat.caveat_id.startswith(caveat_name + " ")] |
234 | + |
235 | + |
236 | +class MacaroonTestMixin: |
237 | + |
238 | + def assertMacaroonVerifies(self, issuer, macaroon, context, **kwargs): |
239 | + errors = [] |
240 | + try: |
241 | + self.assertTrue(issuer.verifyMacaroon( |
242 | + macaroon, context, errors=errors, **kwargs)) |
243 | + except Exception: |
244 | + if errors: |
245 | + self.addDetail("errors", text_content("\n".join(errors))) |
246 | + raise |
247 | + |
248 | + def assertMacaroonDoesNotVerify(self, expected_errors, issuer, macaroon, |
249 | + context, **kwargs): |
250 | + errors = [] |
251 | + self.assertFalse(issuer.verifyMacaroon( |
252 | + macaroon, context, errors=errors, **kwargs)) |
253 | + self.assertEqual(expected_errors, errors) |
254 | |
255 | === modified file 'lib/lp/snappy/tests/test_snapbuild.py' |
256 | --- lib/lp/snappy/tests/test_snapbuild.py 2019-04-25 09:34:40 +0000 |
257 | +++ lib/lp/snappy/tests/test_snapbuild.py 2019-04-25 22:19:14 +0000 |
258 | @@ -46,6 +46,7 @@ |
259 | BadMacaroonContext, |
260 | IMacaroonIssuer, |
261 | ) |
262 | +from lp.services.macaroons.testing import MacaroonTestMixin |
263 | from lp.services.propertycache import clear_property_cache |
264 | from lp.services.webapp.interfaces import OAuthPermission |
265 | from lp.services.webapp.publisher import canonical_url |
266 | @@ -785,7 +786,7 @@ |
267 | self.assertCanOpenRedirectedUrl(browser, file_url) |
268 | |
269 | |
270 | -class TestSnapBuildMacaroonIssuer(TestCaseWithFactory): |
271 | +class TestSnapBuildMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory): |
272 | """Test SnapBuild macaroon issuing and verification.""" |
273 | |
274 | layer = LaunchpadZopelessLayer |
275 | @@ -840,7 +841,7 @@ |
276 | issuer = removeSecurityProxy( |
277 | getUtility(IMacaroonIssuer, "snap-build")) |
278 | macaroon = issuer.issueMacaroon(build) |
279 | - self.assertTrue(issuer.verifyMacaroon(macaroon, ref.repository)) |
280 | + self.assertMacaroonVerifies(issuer, macaroon, ref.repository) |
281 | |
282 | def test_verifyMacaroon_wrong_location(self): |
283 | [ref] = self.factory.makeGitRefs( |
284 | @@ -852,7 +853,9 @@ |
285 | getUtility(IMacaroonIssuer, "snap-build")) |
286 | macaroon = Macaroon( |
287 | location="another-location", key=issuer._root_secret) |
288 | - self.assertFalse(issuer.verifyMacaroon(macaroon, ref.repository)) |
289 | + self.assertMacaroonDoesNotVerify( |
290 | + ["Macaroon has unknown location 'another-location'."], |
291 | + issuer, macaroon, ref.repository) |
292 | |
293 | def test_verifyMacaroon_wrong_key(self): |
294 | [ref] = self.factory.makeGitRefs( |
295 | @@ -864,7 +867,8 @@ |
296 | getUtility(IMacaroonIssuer, "snap-build")) |
297 | macaroon = Macaroon( |
298 | location=config.vhost.mainsite.hostname, key="another-secret") |
299 | - self.assertFalse(issuer.verifyMacaroon(macaroon, ref.repository)) |
300 | + self.assertMacaroonDoesNotVerify( |
301 | + ["Signatures do not match"], issuer, macaroon, ref.repository) |
302 | |
303 | def test_verifyMacaroon_refuses_branch(self): |
304 | branch = self.factory.makeAnyBranch( |
305 | @@ -875,7 +879,8 @@ |
306 | issuer = removeSecurityProxy( |
307 | getUtility(IMacaroonIssuer, "snap-build")) |
308 | macaroon = issuer.issueMacaroon(build) |
309 | - self.assertFalse(issuer.verifyMacaroon(macaroon, branch)) |
310 | + self.assertMacaroonDoesNotVerify( |
311 | + ["Cannot handle context %r." % branch], issuer, macaroon, branch) |
312 | |
313 | def test_verifyMacaroon_not_building(self): |
314 | [ref] = self.factory.makeGitRefs( |
315 | @@ -885,7 +890,9 @@ |
316 | issuer = removeSecurityProxy( |
317 | getUtility(IMacaroonIssuer, "snap-build")) |
318 | macaroon = issuer.issueMacaroon(build) |
319 | - self.assertFalse(issuer.verifyMacaroon(macaroon, ref.repository)) |
320 | + self.assertMacaroonDoesNotVerify( |
321 | + ["Caveat check for 'lp.snap-build %s' failed." % build.id], |
322 | + issuer, macaroon, ref.repository) |
323 | |
324 | def test_verifyMacaroon_wrong_build(self): |
325 | [ref] = self.factory.makeGitRefs( |
326 | @@ -899,7 +906,9 @@ |
327 | issuer = removeSecurityProxy( |
328 | getUtility(IMacaroonIssuer, "snap-build")) |
329 | macaroon = issuer.issueMacaroon(other_build) |
330 | - self.assertFalse(issuer.verifyMacaroon(macaroon, ref.repository)) |
331 | + self.assertMacaroonDoesNotVerify( |
332 | + ["Caveat check for 'lp.snap-build %s' failed." % other_build.id], |
333 | + issuer, macaroon, ref.repository) |
334 | |
335 | def test_verifyMacaroon_wrong_repository(self): |
336 | [ref] = self.factory.makeGitRefs( |
337 | @@ -911,4 +920,6 @@ |
338 | issuer = removeSecurityProxy( |
339 | getUtility(IMacaroonIssuer, "snap-build")) |
340 | macaroon = issuer.issueMacaroon(build) |
341 | - self.assertFalse(issuer.verifyMacaroon(macaroon, other_repository)) |
342 | + self.assertMacaroonDoesNotVerify( |
343 | + ["Caveat check for 'lp.snap-build %s' failed." % build.id], |
344 | + issuer, macaroon, other_repository) |
345 | |
346 | === modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py' |
347 | --- lib/lp/soyuz/tests/test_binarypackagebuild.py 2019-04-25 09:34:40 +0000 |
348 | +++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2019-04-25 22:19:14 +0000 |
349 | @@ -35,6 +35,7 @@ |
350 | BadMacaroonContext, |
351 | IMacaroonIssuer, |
352 | ) |
353 | +from lp.services.macaroons.testing import MacaroonTestMixin |
354 | from lp.services.webapp.interaction import ANONYMOUS |
355 | from lp.services.webapp.interfaces import OAuthPermission |
356 | from lp.soyuz.enums import ( |
357 | @@ -913,7 +914,8 @@ |
358 | archive, getUtility(ILaunchpadCelebrities).ppa_admin) |
359 | |
360 | |
361 | -class TestBinaryPackageBuildMacaroonIssuer(TestCaseWithFactory): |
362 | +class TestBinaryPackageBuildMacaroonIssuer( |
363 | + MacaroonTestMixin, TestCaseWithFactory): |
364 | """Test BinaryPackageBuild macaroon issuing and verification.""" |
365 | |
366 | layer = LaunchpadZopelessLayer |
367 | @@ -961,7 +963,7 @@ |
368 | issuer = removeSecurityProxy( |
369 | getUtility(IMacaroonIssuer, "binary-package-build")) |
370 | macaroon = issuer.issueMacaroon(build) |
371 | - self.assertTrue(issuer.verifyMacaroon(macaroon, sprf.libraryfile)) |
372 | + self.assertMacaroonVerifies(issuer, macaroon, sprf.libraryfile) |
373 | |
374 | def test_verifyMacaroon_wrong_location(self): |
375 | build = self.factory.makeBinaryPackageBuild( |
376 | @@ -973,7 +975,9 @@ |
377 | getUtility(IMacaroonIssuer, "binary-package-build")) |
378 | macaroon = Macaroon( |
379 | location="another-location", key=issuer._root_secret) |
380 | - self.assertFalse(issuer.verifyMacaroon(macaroon, sprf.libraryfile)) |
381 | + self.assertMacaroonDoesNotVerify( |
382 | + ["Macaroon has unknown location 'another-location'."], |
383 | + issuer, macaroon, sprf.libraryfile) |
384 | |
385 | def test_verifyMacaroon_wrong_key(self): |
386 | build = self.factory.makeBinaryPackageBuild( |
387 | @@ -985,7 +989,8 @@ |
388 | getUtility(IMacaroonIssuer, "binary-package-build")) |
389 | macaroon = Macaroon( |
390 | location=config.vhost.mainsite.hostname, key="another-secret") |
391 | - self.assertFalse(issuer.verifyMacaroon(macaroon, sprf.libraryfile)) |
392 | + self.assertMacaroonDoesNotVerify( |
393 | + ["Signatures do not match"], issuer, macaroon, sprf.libraryfile) |
394 | |
395 | def test_verifyMacaroon_not_building(self): |
396 | build = self.factory.makeBinaryPackageBuild( |
397 | @@ -995,7 +1000,10 @@ |
398 | issuer = removeSecurityProxy( |
399 | getUtility(IMacaroonIssuer, "binary-package-build")) |
400 | macaroon = issuer.issueMacaroon(build) |
401 | - self.assertFalse(issuer.verifyMacaroon(macaroon, sprf.libraryfile)) |
402 | + self.assertMacaroonDoesNotVerify( |
403 | + ["Caveat check for 'lp.principal.binary-package-build %s' " |
404 | + "failed." % build.id], |
405 | + issuer, macaroon, sprf.libraryfile) |
406 | |
407 | def test_verifyMacaroon_wrong_build(self): |
408 | build = self.factory.makeBinaryPackageBuild( |
409 | @@ -1009,7 +1017,10 @@ |
410 | issuer = removeSecurityProxy( |
411 | getUtility(IMacaroonIssuer, "binary-package-build")) |
412 | macaroon = issuer.issueMacaroon(other_build) |
413 | - self.assertFalse(issuer.verifyMacaroon(macaroon, sprf.libraryfile)) |
414 | + self.assertMacaroonDoesNotVerify( |
415 | + ["Caveat check for 'lp.principal.binary-package-build %s' " |
416 | + "failed." % other_build.id], |
417 | + issuer, macaroon, sprf.libraryfile) |
418 | |
419 | def test_verifyMacaroon_wrong_file(self): |
420 | build = self.factory.makeBinaryPackageBuild( |
421 | @@ -1021,4 +1032,7 @@ |
422 | issuer = removeSecurityProxy( |
423 | getUtility(IMacaroonIssuer, "binary-package-build")) |
424 | macaroon = issuer.issueMacaroon(build) |
425 | - self.assertFalse(issuer.verifyMacaroon(macaroon, lfa)) |
426 | + self.assertMacaroonDoesNotVerify( |
427 | + ["Caveat check for 'lp.principal.binary-package-build %s' " |
428 | + "failed." % build.id], |
429 | + issuer, macaroon, lfa) |
I wonder if it's better to refactor this to return a list of violations rather than directly logging them and returning False. Some callsites can then log without context if we really want, but the tests can do it less evilly.