Merge lp:~adeuring/launchpad/sharingjob-remove-bp-subscriptions into lp:launchpad
- sharingjob-remove-bp-subscriptions
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Abel Deuring |
Approved revision: | no longer in the source branch. |
Merged at revision: | 16015 |
Proposed branch: | lp:~adeuring/launchpad/sharingjob-remove-bp-subscriptions |
Merge into: | lp:launchpad |
Diff against target: |
616 lines (+299/-87) 5 files modified
database/schema/security.cfg (+2/-0) lib/lp/blueprints/model/specification.py (+57/-2) lib/lp/blueprints/tests/test_specification.py (+52/-1) lib/lp/registry/model/sharingjob.py (+53/-0) lib/lp/registry/tests/test_sharingjob.py (+135/-84) |
To merge this branch: | bzr merge lp:~adeuring/launchpad/sharingjob-remove-bp-subscriptions |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | code | Approve | |
Review via email: mp+126008@code.launchpad.net |
Commit message
specifications added to RemoveArtifactS
Description of the change
This branch extends RemoveArtifactS
removes subscriptions to specifications if a subscriber does no longer
have grants to access these specifications.
The change in lib/lp/
a mechanical change that adds specification handling to bug and branch
handling.
The method issues a Storm query to find subscribers of a specification
without access grants. The Storm expression "person with[out] access
to a spec" is defined in a new function get_specificati
similar to the existing functions get_bug_
get_branch_
RemoveArtifactS
arbitrary objects, which I found a bit confusing while working on this
branch, so I added a check to ensure that only those objects can be
passed to this object that the class can handle, i.e. bugs, branches
and blueprints.
Many tests of RemoveArtifactS
_assert_
which were quite long but nearly identical. The new test
test_change_
variant of these methods, _assert_
I refactored all three _assert.* methods to use a common method.
tests:
./bin/test -vvt lp.registry.
./bin/test -vvt lp.blueprints.
no lint
Abel Deuring (adeuring) wrote : | # |
Benji, thanks for these suggestions. I chose the first variant, moved the "if test_type == " blocks into the callsites. _assert_
Benji York (benji) wrote : | # |
On Mon, Sep 24, 2012 at 2:16 PM, Abel Deuring
<email address hidden> wrote:
> Benji, thanks for these suggestions. I chose the first variant, moved the "if
> test_type == " blocks into the callsites.
> _assert_
> bit more readable...
Cool.
Preview Diff
1 | === modified file 'database/schema/security.cfg' |
2 | --- database/schema/security.cfg 2012-09-21 00:08:41 +0000 |
3 | +++ database/schema/security.cfg 2012-09-24 14:20:26 +0000 |
4 | @@ -1966,6 +1966,8 @@ |
5 | public.person = SELECT |
6 | public.product = SELECT |
7 | public.sharingjob = SELECT, INSERT, UPDATE |
8 | +public.specification = SELECT |
9 | +public.specificationsubscription = SELECT, DELETE |
10 | public.teamparticipation = SELECT |
11 | type=user |
12 | |
13 | |
14 | === modified file 'lib/lp/blueprints/model/specification.py' |
15 | --- lib/lp/blueprints/model/specification.py 2012-09-18 19:41:02 +0000 |
16 | +++ lib/lp/blueprints/model/specification.py 2012-09-24 14:20:26 +0000 |
17 | @@ -5,6 +5,7 @@ |
18 | |
19 | __metaclass__ = type |
20 | __all__ = [ |
21 | + 'get_specification_privacy_filter', |
22 | 'HasSpecificationsMixin', |
23 | 'recursive_blocked_query', |
24 | 'recursive_dependent_query', |
25 | @@ -31,6 +32,7 @@ |
26 | And, |
27 | In, |
28 | Join, |
29 | + LeftJoin, |
30 | Or, |
31 | Select, |
32 | ) |
33 | @@ -724,14 +726,15 @@ |
34 | notify(ObjectCreatedEvent(sub, user=subscribed_by)) |
35 | return sub |
36 | |
37 | - def unsubscribe(self, person, unsubscribed_by): |
38 | + def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False): |
39 | """See ISpecification.""" |
40 | # see if a relevant subscription exists, and if so, delete it |
41 | if person is None: |
42 | person = unsubscribed_by |
43 | for sub in self.subscriptions: |
44 | if sub.person.id == person.id: |
45 | - if not sub.canBeUnsubscribedByUser(unsubscribed_by): |
46 | + if (not sub.canBeUnsubscribedByUser(unsubscribed_by) and |
47 | + not ignore_permissions): |
48 | raise UserCannotUnsubscribePerson( |
49 | '%s does not have permission to unsubscribe %s.' % ( |
50 | unsubscribed_by.displayname, |
51 | @@ -1226,3 +1229,55 @@ |
52 | def get(self, spec_id): |
53 | """See lp.blueprints.interfaces.specification.ISpecificationSet.""" |
54 | return Specification.get(spec_id) |
55 | + |
56 | + |
57 | +def get_specification_privacy_filter(user): |
58 | + """Return a Storm expression for filtering specifications by privacy. |
59 | + |
60 | + :param user: A Person ID or a column reference. |
61 | + :return: A Storm expression to check if a peron has access grants |
62 | + for a specification. |
63 | + """ |
64 | + # Avoid circular imports. |
65 | + from lp.registry.model.accesspolicy import ( |
66 | + AccessArtifact, |
67 | + AccessPolicy, |
68 | + AccessPolicyGrantFlat, |
69 | + ) |
70 | + public_specification_filter = ( |
71 | + Specification.information_type.is_in(PUBLIC_INFORMATION_TYPES)) |
72 | + if user is None: |
73 | + return public_specification_filter |
74 | + return Or( |
75 | + public_specification_filter, |
76 | + Specification.id.is_in( |
77 | + Select( |
78 | + Specification.id, |
79 | + tables=( |
80 | + Specification, |
81 | + Join( |
82 | + AccessPolicy, |
83 | + And( |
84 | + Or( |
85 | + Specification.productID == |
86 | + AccessPolicy.product_id, |
87 | + Specification.distributionID == |
88 | + AccessPolicy.distribution_id), |
89 | + Specification.information_type == |
90 | + AccessPolicy.type)), |
91 | + Join( |
92 | + AccessPolicyGrantFlat, |
93 | + AccessPolicy.id == AccessPolicyGrantFlat.policy_id), |
94 | + LeftJoin( |
95 | + AccessArtifact, |
96 | + AccessPolicyGrantFlat.abstract_artifact_id == |
97 | + AccessArtifact.id), |
98 | + Join( |
99 | + TeamParticipation, |
100 | + And( |
101 | + TeamParticipation.team == |
102 | + AccessPolicyGrantFlat.grantee_id, |
103 | + TeamParticipation.person == user))), |
104 | + where=Or( |
105 | + AccessPolicyGrantFlat.abstract_artifact_id == None, |
106 | + AccessArtifact.specification_id == Specification.id)))) |
107 | |
108 | === modified file 'lib/lp/blueprints/tests/test_specification.py' |
109 | --- lib/lp/blueprints/tests/test_specification.py 2012-09-17 15:19:10 +0000 |
110 | +++ lib/lp/blueprints/tests/test_specification.py 2012-09-24 14:20:26 +0000 |
111 | @@ -6,6 +6,7 @@ |
112 | __metaclass__ = type |
113 | |
114 | |
115 | +from storm.store import Store |
116 | from zope.component import ( |
117 | getUtility, |
118 | queryAdapter, |
119 | @@ -31,7 +32,14 @@ |
120 | ) |
121 | from lp.blueprints.errors import TargetAlreadyHasSpecification |
122 | from lp.blueprints.interfaces.specification import ISpecificationSet |
123 | -from lp.registry.enums import SharingPermission |
124 | +from lp.blueprints.model.specification import ( |
125 | + get_specification_privacy_filter, |
126 | + Specification, |
127 | + ) |
128 | +from lp.registry.enums import ( |
129 | + SharingPermission, |
130 | + SpecificationSharingPolicy, |
131 | + ) |
132 | from lp.security import ( |
133 | AdminSpecification, |
134 | EditSpecificationByRelatedPeople, |
135 | @@ -409,6 +417,49 @@ |
136 | specification.target.owner, specification, |
137 | error_expected=False, attribute='name', value='foo') |
138 | |
139 | + def test_get_specification_privacy_filter(self): |
140 | + # get_specification_privacy_filter() returns a Storm expression |
141 | + # that can be used to filter specifications by their visibility- |
142 | + owner = self.factory.makePerson() |
143 | + product = self.factory.makeProduct( |
144 | + owner=owner, |
145 | + specification_sharing_policy=( |
146 | + SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY)) |
147 | + public_spec = self.factory.makeSpecification(product=product) |
148 | + proprietary_spec_1 = self.factory.makeSpecification( |
149 | + product=product, information_type=InformationType.PROPRIETARY) |
150 | + proprietary_spec_2 = self.factory.makeSpecification( |
151 | + product=product, information_type=InformationType.PROPRIETARY) |
152 | + all_specs = [ |
153 | + public_spec, proprietary_spec_1, proprietary_spec_2] |
154 | + store = Store.of(product) |
155 | + specs_for_anon = store.find( |
156 | + Specification, get_specification_privacy_filter(None), |
157 | + Specification.productID == product.id) |
158 | + self.assertContentEqual([public_spec], specs_for_anon) |
159 | + # Product owners havae grants on the product, the privacy |
160 | + # filter returns thus all specifications for them. |
161 | + specs_for_owner = store.find( |
162 | + Specification, get_specification_privacy_filter(owner.id), |
163 | + Specification.productID == product.id) |
164 | + self.assertContentEqual(all_specs, specs_for_owner) |
165 | + # The filter returns only public specs for ordinary users. |
166 | + user = self.factory.makePerson() |
167 | + specs_for_other_user = store.find( |
168 | + Specification, get_specification_privacy_filter(user.id), |
169 | + Specification.productID == product.id) |
170 | + self.assertContentEqual([public_spec], specs_for_other_user) |
171 | + # If the user has a grant for a specification, the filter returns |
172 | + # this specification too. |
173 | + with person_logged_in(owner): |
174 | + getUtility(IService, 'sharing').ensureAccessGrants( |
175 | + [user], owner, specifications=[proprietary_spec_1]) |
176 | + specs_for_other_user = store.find( |
177 | + Specification, get_specification_privacy_filter(user.id), |
178 | + Specification.productID == product.id) |
179 | + self.assertContentEqual( |
180 | + [public_spec, proprietary_spec_1], specs_for_other_user) |
181 | + |
182 | |
183 | class TestSpecificationSet(TestCaseWithFactory): |
184 | |
185 | |
186 | === modified file 'lib/lp/registry/model/sharingjob.py' |
187 | --- lib/lp/registry/model/sharingjob.py 2012-09-18 19:41:02 +0000 |
188 | +++ lib/lp/registry/model/sharingjob.py 2012-09-24 14:20:26 +0000 |
189 | @@ -46,6 +46,14 @@ |
190 | IBug, |
191 | IBugSet, |
192 | ) |
193 | +from lp.blueprints.interfaces.specification import ISpecification |
194 | +from lp.blueprints.model.specification import ( |
195 | + get_specification_privacy_filter, |
196 | + Specification, |
197 | + ) |
198 | +from lp.blueprints.model.specificationsubscription import ( |
199 | + SpecificationSubscription, |
200 | + ) |
201 | from lp.bugs.model.bugsubscription import BugSubscription |
202 | from lp.bugs.model.bugtaskflat import BugTaskFlat |
203 | from lp.bugs.model.bugtasksearch import get_bug_privacy_filter_terms |
204 | @@ -265,18 +273,25 @@ |
205 | |
206 | bug_ids = [] |
207 | branch_ids = [] |
208 | + specification_ids = [] |
209 | if artifacts: |
210 | for artifact in artifacts: |
211 | if IBug.providedBy(artifact): |
212 | bug_ids.append(artifact.id) |
213 | elif IBranch.providedBy(artifact): |
214 | branch_ids.append(artifact.id) |
215 | + elif ISpecification.providedBy(artifact): |
216 | + specification_ids.append(artifact.id) |
217 | + else: |
218 | + raise ValueError( |
219 | + 'Unsupported artifact: %r' % artifact) |
220 | information_types = [ |
221 | info_type.value for info_type in information_types or [] |
222 | ] |
223 | metadata = { |
224 | 'bug_ids': bug_ids, |
225 | 'branch_ids': branch_ids, |
226 | + 'specification_ids': specification_ids, |
227 | 'information_types': information_types, |
228 | 'requestor.id': requestor.id |
229 | } |
230 | @@ -308,6 +323,10 @@ |
231 | return [getUtility(IBranchLookup).get(id) for id in self.branch_ids] |
232 | |
233 | @property |
234 | + def specification_ids(self): |
235 | + return self.metadata.get('specification_ids', []) |
236 | + |
237 | + @property |
238 | def information_types(self): |
239 | if not 'information_types' in self.metadata: |
240 | return [] |
241 | @@ -332,6 +351,7 @@ |
242 | 'requestor': self.requestor.name, |
243 | 'bug_ids': self.bug_ids, |
244 | 'branch_ids': self.branch_ids, |
245 | + 'specification_ids': self.specification_ids, |
246 | 'pillar': getattr(self.pillar, 'name', None), |
247 | 'grantee': getattr(self.grantee, 'name', None) |
248 | } |
249 | @@ -346,9 +366,13 @@ |
250 | |
251 | bug_filters = [] |
252 | branch_filters = [] |
253 | + specification_filters = [] |
254 | |
255 | if self.branch_ids: |
256 | branch_filters.append(Branch.id.is_in(self.branch_ids)) |
257 | + if self.specification_ids: |
258 | + specification_filters.append(Specification.id.is_in( |
259 | + self.specification_ids)) |
260 | if self.bug_ids: |
261 | bug_filters.append(BugTaskFlat.bug_id.is_in(self.bug_ids)) |
262 | else: |
263 | @@ -358,14 +382,21 @@ |
264 | self.information_types)) |
265 | branch_filters.append( |
266 | Branch.information_type.is_in(self.information_types)) |
267 | + specification_filters.append( |
268 | + Specification.information_type.is_in( |
269 | + self.information_types)) |
270 | if self.product: |
271 | bug_filters.append( |
272 | BugTaskFlat.product == self.product) |
273 | branch_filters.append(Branch.product == self.product) |
274 | + specification_filters.append( |
275 | + Specification.product == self.product) |
276 | if self.distro: |
277 | bug_filters.append( |
278 | BugTaskFlat.distribution == self.distro) |
279 | branch_filters.append(Branch.distribution == self.distro) |
280 | + specification_filters.append( |
281 | + Specification.distribution == self.distro) |
282 | |
283 | if self.grantee: |
284 | bug_filters.append( |
285 | @@ -378,6 +409,11 @@ |
286 | Select( |
287 | TeamParticipation.personID, |
288 | where=TeamParticipation.team == self.grantee))) |
289 | + specification_filters.append( |
290 | + In(SpecificationSubscription.personID, |
291 | + Select( |
292 | + TeamParticipation.personID, |
293 | + where=TeamParticipation.team == self.grantee))) |
294 | |
295 | if bug_filters: |
296 | bug_filters.append(Not( |
297 | @@ -402,3 +438,20 @@ |
298 | for sub in branch_subscriptions: |
299 | sub.branch.unsubscribe( |
300 | sub.person, self.requestor, ignore_permissions=True) |
301 | + if specification_filters: |
302 | + specification_filters.append( |
303 | + Not(get_specification_privacy_filter( |
304 | + SpecificationSubscription.personID))) |
305 | + tables = ( |
306 | + SpecificationSubscription, |
307 | + Join( |
308 | + Specification, |
309 | + Specification.id == |
310 | + SpecificationSubscription.specificationID)) |
311 | + specifications_subscriptions = IStore( |
312 | + SpecificationSubscription).using(*tables).find( |
313 | + SpecificationSubscription, *specification_filters).config( |
314 | + distinct=True) |
315 | + for sub in specifications_subscriptions: |
316 | + sub.specification.unsubscribe( |
317 | + sub.person, self.requestor, ignore_permissions=True) |
318 | |
319 | === modified file 'lib/lp/registry/tests/test_sharingjob.py' |
320 | --- lib/lp/registry/tests/test_sharingjob.py 2012-09-18 19:41:02 +0000 |
321 | +++ lib/lp/registry/tests/test_sharingjob.py 2012-09-24 14:20:26 +0000 |
322 | @@ -17,6 +17,7 @@ |
323 | BranchSubscriptionNotificationLevel, |
324 | CodeReviewNotificationLevel, |
325 | ) |
326 | +from lp.registry.enums import SpecificationSharingPolicy |
327 | from lp.registry.interfaces.accesspolicy import ( |
328 | IAccessArtifactGrantSource, |
329 | IAccessPolicySource, |
330 | @@ -117,6 +118,16 @@ |
331 | 'for branch_ids=[%d], requestor=%s>' |
332 | % (branch.id, requestor.name), repr(job)) |
333 | |
334 | + def test_repr_specifications(self): |
335 | + requestor = self.factory.makePerson() |
336 | + specification = self.factory.makeSpecification() |
337 | + job = getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
338 | + requestor, artifacts=[specification]) |
339 | + self.assertEqual( |
340 | + '<REMOVE_ARTIFACT_SUBSCRIPTIONS job reconciling subscriptions ' |
341 | + 'for requestor=%s, specification_ids=[%d]>' |
342 | + % (requestor.name, specification.id), repr(job)) |
343 | + |
344 | def test_create_success(self): |
345 | # Create an instance of SharingJobDerived that delegates to SharingJob. |
346 | self.assertIs(True, ISharingJobSource.providedBy(SharingJobDerived)) |
347 | @@ -244,6 +255,17 @@ |
348 | self.assertEqual(requestor.id, naked_job.requestor_id) |
349 | self.assertContentEqual([bug.id], naked_job.bug_ids) |
350 | |
351 | + def test_create__bad_artifact_class(self): |
352 | + # A ValueError is raised if an object of an unsupported type |
353 | + # is passed as an artifact to |
354 | + # IRemoveArtifactSubscriptionsJob.create(). |
355 | + requestor = self.factory.makePerson() |
356 | + wrong_object = self.factory.makePerson() |
357 | + self.assertRaises( |
358 | + ValueError, |
359 | + getUtility(IRemoveArtifactSubscriptionsJobSource).create, |
360 | + requestor, [wrong_object]) |
361 | + |
362 | def test_getErrorRecipients(self): |
363 | # The pillar owner and job requestor are the error recipients. |
364 | requestor = self.factory.makePerson() |
365 | @@ -257,10 +279,15 @@ |
366 | self.assertContentEqual( |
367 | expected_emails, job.getErrorRecipients()) |
368 | |
369 | - def _assert_bug_change_unsubscribes(self, change_callback): |
370 | - # Subscribers are unsubscribed if the bug becomes invisible due to a |
371 | - # change in information_type. |
372 | - product = self.factory.makeProduct() |
373 | + def _assert_artifact_change_unsubscribes(self, change_callback, |
374 | + configure_test): |
375 | + # Subscribers are unsubscribed if the artifact becomes invisible |
376 | + # due to a change in information_type. |
377 | + product = self.factory.makeProduct( |
378 | + specification_sharing_policy=( |
379 | + SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY)) |
380 | + self.factory.makeAccessPolicy(product, InformationType.PROPRIETARY) |
381 | + self.factory.makeAccessPolicy(product, InformationType.EMBARGOED) |
382 | owner = self.factory.makePerson() |
383 | [policy] = getUtility(IAccessPolicySource).find( |
384 | [(product, InformationType.USERDATA)]) |
385 | @@ -278,6 +305,9 @@ |
386 | branch = self.factory.makeBranch( |
387 | owner=owner, product=product, |
388 | information_type=InformationType.USERDATA) |
389 | + specification = self.factory.makeSpecification( |
390 | + owner=owner, product=product, |
391 | + information_type=InformationType.PROPRIETARY) |
392 | |
393 | # The artifact grantees will not lose access when the job is run. |
394 | artifact_indirect_grantee = self.factory.makePerson() |
395 | @@ -285,113 +315,127 @@ |
396 | membership_policy=TeamMembershipPolicy.RESTRICTED, |
397 | members=[artifact_indirect_grantee]) |
398 | |
399 | - bug.subscribe(policy_team_grantee, owner) |
400 | - bug.subscribe(policy_indirect_grantee, owner) |
401 | - bug.subscribe(artifact_team_grantee, owner) |
402 | bug.subscribe(artifact_indirect_grantee, owner) |
403 | branch.subscribe(artifact_indirect_grantee, |
404 | BranchSubscriptionNotificationLevel.NOEMAIL, None, |
405 | CodeReviewNotificationLevel.NOEMAIL, owner) |
406 | + # Subscribing somebody to a specification does not automatically |
407 | + # create an artifact grant. |
408 | + spec_artifact = self.factory.makeAccessArtifact(specification) |
409 | + self.factory.makeAccessArtifactGrant( |
410 | + spec_artifact, artifact_indirect_grantee) |
411 | + specification.subscribe(artifact_indirect_grantee, owner) |
412 | + |
413 | + # pick one of the concrete artifacts (bug, branch or spec) |
414 | + # and subscribe the teams and persons. |
415 | + concrete_artifact, get_pillars, get_subscribers = configure_test( |
416 | + bug, branch, specification, policy_team_grantee, |
417 | + policy_indirect_grantee, artifact_team_grantee, owner) |
418 | + |
419 | # Subscribing policy_team_grantee has created an artifact grant so we |
420 | # need to revoke that to test the job. |
421 | - artifact = self.factory.makeAccessArtifact(concrete=bug) |
422 | + artifact = self.factory.makeAccessArtifact(concrete=concrete_artifact) |
423 | getUtility(IAccessArtifactGrantSource).revokeByArtifact( |
424 | [artifact], [policy_team_grantee]) |
425 | |
426 | # policy grantees are subscribed because the job has not been run yet. |
427 | - subscribers = removeSecurityProxy(bug).getDirectSubscribers() |
428 | + subscribers = get_subscribers(concrete_artifact) |
429 | self.assertIn(policy_team_grantee, subscribers) |
430 | self.assertIn(policy_indirect_grantee, subscribers) |
431 | |
432 | - # Change bug attributes so that it can become inaccessible for |
433 | + # Change artifact attributes so that it can become inaccessible for |
434 | # some users. |
435 | - change_callback(bug) |
436 | + change_callback(concrete_artifact) |
437 | reconcile_access_for_artifact( |
438 | - bug, bug.information_type, bug.affected_pillars) |
439 | + concrete_artifact, concrete_artifact.information_type, |
440 | + get_pillars(concrete_artifact)) |
441 | |
442 | getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
443 | - owner, [bug]) |
444 | + owner, [concrete_artifact]) |
445 | with block_on_job(self): |
446 | transaction.commit() |
447 | |
448 | # Check the result. Policy grantees will be unsubscribed. |
449 | - subscribers = removeSecurityProxy(bug).getDirectSubscribers() |
450 | + subscribers = get_subscribers(concrete_artifact) |
451 | self.assertNotIn(policy_team_grantee, subscribers) |
452 | self.assertNotIn(policy_indirect_grantee, subscribers) |
453 | self.assertIn(artifact_team_grantee, subscribers) |
454 | - self.assertIn(artifact_indirect_grantee, subscribers) |
455 | + self.assertIn(artifact_indirect_grantee, bug.getDirectSubscribers()) |
456 | self.assertIn(artifact_indirect_grantee, branch.subscribers) |
457 | + self.assertIn(artifact_indirect_grantee, specification.subscribers) |
458 | + |
459 | + def _assert_bug_change_unsubscribes(self, change_callback): |
460 | + |
461 | + def get_pillars(concrete_artifact): |
462 | + return concrete_artifact.affected_pillars |
463 | + |
464 | + def get_subscribers(concrete_artifact): |
465 | + return removeSecurityProxy( |
466 | + concrete_artifact).getDirectSubscribers() |
467 | + |
468 | + def configure_test(bug, branch, specification, policy_team_grantee, |
469 | + policy_indirect_grantee, artifact_team_grantee, |
470 | + owner): |
471 | + concrete_artifact = bug |
472 | + bug.subscribe(policy_team_grantee, owner) |
473 | + bug.subscribe(policy_indirect_grantee, owner) |
474 | + bug.subscribe(artifact_team_grantee, owner) |
475 | + return concrete_artifact, get_pillars, get_subscribers |
476 | + |
477 | + self._assert_artifact_change_unsubscribes( |
478 | + change_callback, configure_test) |
479 | |
480 | def _assert_branch_change_unsubscribes(self, change_callback): |
481 | - product = self.factory.makeProduct() |
482 | - owner = self.factory.makePerson() |
483 | - [policy] = getUtility(IAccessPolicySource).find( |
484 | - [(product, InformationType.USERDATA)]) |
485 | - # The policy grantees will lose access. |
486 | - policy_indirect_grantee = self.factory.makePerson() |
487 | - policy_team_grantee = self.factory.makeTeam( |
488 | - membership_policy=TeamMembershipPolicy.RESTRICTED, |
489 | - members=[policy_indirect_grantee]) |
490 | - |
491 | - self.factory.makeAccessPolicyGrant(policy, policy_team_grantee, owner) |
492 | - login_person(owner) |
493 | - bug = self.factory.makeBug( |
494 | - owner=owner, target=product, |
495 | - information_type=InformationType.USERDATA) |
496 | - branch = self.factory.makeBranch( |
497 | - owner=owner, product=product, |
498 | - information_type=InformationType.USERDATA) |
499 | - |
500 | - # The artifact grantees will not lose access when the job is run. |
501 | - artifact_indirect_grantee = self.factory.makePerson() |
502 | - artifact_team_grantee = self.factory.makeTeam( |
503 | - membership_policy=TeamMembershipPolicy.RESTRICTED, |
504 | - members=[artifact_indirect_grantee]) |
505 | - |
506 | - branch.subscribe( |
507 | - policy_team_grantee, BranchSubscriptionNotificationLevel.NOEMAIL, |
508 | - None, CodeReviewNotificationLevel.NOEMAIL, owner) |
509 | - branch.subscribe( |
510 | - policy_indirect_grantee, |
511 | - BranchSubscriptionNotificationLevel.NOEMAIL, None, |
512 | - CodeReviewNotificationLevel.NOEMAIL, owner) |
513 | - branch.subscribe( |
514 | - artifact_team_grantee, |
515 | - BranchSubscriptionNotificationLevel.NOEMAIL, None, |
516 | - CodeReviewNotificationLevel.NOEMAIL, owner) |
517 | - branch.subscribe( |
518 | - artifact_indirect_grantee, |
519 | - BranchSubscriptionNotificationLevel.NOEMAIL, None, |
520 | - CodeReviewNotificationLevel.NOEMAIL, owner) |
521 | - bug.subscribe(artifact_indirect_grantee, owner) |
522 | - # Subscribing policy_team_grantee has created an artifact grant so we |
523 | - # need to revoke that to test the job. |
524 | - artifact = self.factory.makeAccessArtifact(concrete=branch) |
525 | - getUtility(IAccessArtifactGrantSource).revokeByArtifact( |
526 | - [artifact], [policy_team_grantee]) |
527 | - |
528 | - # policy grantees are subscribed because the job has not been run yet. |
529 | - #subscribers = removeSecurityProxy(branch).subscribers |
530 | - self.assertIn(policy_team_grantee, branch.subscribers) |
531 | - self.assertIn(policy_indirect_grantee, branch.subscribers) |
532 | - |
533 | - # Change branch attributes so that it can become inaccessible for |
534 | - # some users. |
535 | - change_callback(branch) |
536 | - reconcile_access_for_artifact( |
537 | - branch, branch.information_type, [branch.product]) |
538 | - |
539 | - getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
540 | - owner, [branch]) |
541 | - with block_on_job(self): |
542 | - transaction.commit() |
543 | - |
544 | - # Check the result. Policy grantees will be unsubscribed. |
545 | - self.assertNotIn(policy_team_grantee, branch.subscribers) |
546 | - self.assertNotIn(policy_indirect_grantee, branch.subscribers) |
547 | - self.assertIn(artifact_team_grantee, branch.subscribers) |
548 | - self.assertIn(artifact_indirect_grantee, branch.subscribers) |
549 | - self.assertIn(artifact_indirect_grantee, bug.getDirectSubscribers()) |
550 | + |
551 | + def get_pillars(concrete_artifact): |
552 | + return [concrete_artifact.product] |
553 | + |
554 | + def get_subscribers(concrete_artifact): |
555 | + return concrete_artifact.subscribers |
556 | + |
557 | + def configure_test(bug, branch, specification, policy_team_grantee, |
558 | + policy_indirect_grantee, artifact_team_grantee, |
559 | + owner): |
560 | + concrete_artifact = branch |
561 | + branch.subscribe( |
562 | + policy_team_grantee, |
563 | + BranchSubscriptionNotificationLevel.NOEMAIL, |
564 | + None, CodeReviewNotificationLevel.NOEMAIL, owner) |
565 | + branch.subscribe( |
566 | + policy_indirect_grantee, |
567 | + BranchSubscriptionNotificationLevel.NOEMAIL, None, |
568 | + CodeReviewNotificationLevel.NOEMAIL, owner) |
569 | + branch.subscribe( |
570 | + artifact_team_grantee, |
571 | + BranchSubscriptionNotificationLevel.NOEMAIL, None, |
572 | + CodeReviewNotificationLevel.NOEMAIL, owner) |
573 | + return concrete_artifact, get_pillars, get_subscribers |
574 | + |
575 | + self._assert_artifact_change_unsubscribes( |
576 | + change_callback, configure_test) |
577 | + |
578 | + def _assert_specification_change_unsubscribes(self, change_callback): |
579 | + |
580 | + def get_pillars(concrete_artifact): |
581 | + return [concrete_artifact.product] |
582 | + |
583 | + def get_subscribers(concrete_artifact): |
584 | + return concrete_artifact.subscribers |
585 | + |
586 | + def configure_test(bug, branch, specification, policy_team_grantee, |
587 | + policy_indirect_grantee, artifact_team_grantee, |
588 | + owner): |
589 | + concrete_artifact = specification |
590 | + specification.subscribe(policy_team_grantee, owner) |
591 | + specification.subscribe(policy_indirect_grantee, owner) |
592 | + spec_artifact = self.factory.makeAccessArtifact(specification) |
593 | + self.factory.makeAccessArtifactGrant( |
594 | + spec_artifact, artifact_team_grantee) |
595 | + specification.subscribe(artifact_team_grantee, owner) |
596 | + return concrete_artifact, get_pillars, get_subscribers |
597 | + |
598 | + self._assert_artifact_change_unsubscribes( |
599 | + change_callback, configure_test) |
600 | |
601 | def test_change_information_type_branch(self): |
602 | def change_information_type(branch): |
603 | @@ -400,6 +444,13 @@ |
604 | |
605 | self._assert_branch_change_unsubscribes(change_information_type) |
606 | |
607 | + def test_change_information_type_specification(self): |
608 | + def change_information_type(specification): |
609 | + removeSecurityProxy(specification).information_type = ( |
610 | + InformationType.EMBARGOED) |
611 | + |
612 | + self._assert_specification_change_unsubscribes(change_information_type) |
613 | + |
614 | def test_change_information_type(self): |
615 | # Changing the information type of a bug unsubscribes users who can no |
616 | # longer see the bug. |
This branch looks good. Here are some minor comments:
It seems you have an extra underscore in the test name _bad_artifact_ class".
"test_create_
I like the generalization of _assert_ artifact_ change_ unsubscribes. It is
unfortunate that the function is so long though. I wonder if instead of
passing in a test type we could instead break out the contents of that if block
into functions that are instead passed in and called.
Another idea: break the _assert_ artifact_ change_ unsubscribes into two
functions, one for the code before "if test_type" and one after. The
individual assertions could then call the first function, then include their
unique code (currently contained in the if) and then finally call the last
function.