launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #08482
[Merge] lp:~wgrant/launchpad/branch-apas into lp:launchpad
William Grant has proposed merging lp:~wgrant/launchpad/branch-apas into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~wgrant/launchpad/branch-apas/+merge/108532
This branch implements AccessArtifact and AccessPolicyArtifact maintenance for private branches. It fairly extensively refactors my bug work from last week.
Note that, unlike the bug work, this is not replacing a database trigger. There is no existing implementation to match.
--
https://code.launchpad.net/~wgrant/launchpad/branch-apas/+merge/108532
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/branch-apas into lp:launchpad.
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2012-06-02 13:12:32 +0000
+++ database/schema/security.cfg 2012-06-04 09:50:27 +0000
@@ -687,6 +687,9 @@
type=user
[branch-distro]
+public.accessartifact = SELECT, INSERT, DELETE
+public.accesspolicy = SELECT
+public.accesspolicyartifact = SELECT, INSERT, DELETE
public.branch = SELECT, INSERT, UPDATE
public.branchrevision = SELECT, INSERT
public.branchsubscription = SELECT, INSERT
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2012-06-02 13:12:32 +0000
+++ lib/lp/bugs/model/bug.py 2012-06-04 09:50:27 +0000
@@ -159,15 +159,9 @@
from lp.hardwaredb.interfaces.hwdb import IHWSubmissionBugSet
from lp.registry.enums import (
InformationType,
- PUBLIC_INFORMATION_TYPES,
PRIVATE_INFORMATION_TYPES,
SECURITY_INFORMATION_TYPES,
)
-from lp.registry.interfaces.accesspolicy import (
- IAccessArtifactSource,
- IAccessPolicySource,
- IAccessPolicyArtifactSource,
- )
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.person import (
@@ -180,6 +174,7 @@
from lp.registry.interfaces.role import IPersonRoles
from lp.registry.interfaces.series import SeriesStatus
from lp.registry.interfaces.sourcepackage import ISourcePackage
+from lp.registry.model.accesspolicy import reconcile_access_for_artifact
from lp.registry.model.person import (
Person,
person_sort_key,
@@ -1786,7 +1781,7 @@
self.subscribe(s, who)
self.information_type = information_type
- self.updateAccessPolicyArtifacts()
+ self._reconcileAccess()
self.updateHeat()
return True
@@ -2166,27 +2161,9 @@
self.heat_last_updated = UTC_NOW
store.flush()
- def updateAccessPolicyArtifacts(self):
- if self.information_type in PUBLIC_INFORMATION_TYPES:
- # If it's public we can delete all the access information.
- # IAccessArtifactSource handles the cascade.
- getUtility(IAccessArtifactSource).delete([self])
- return
- [artifact] = getUtility(IAccessArtifactSource).ensure([self])
-
- # Now determine the existing and desired links, and make them
- # match.
- apasource = getUtility(IAccessPolicyArtifactSource)
- wanted_links = set(
- (artifact, policy) for policy in
- getUtility(IAccessPolicySource).find(
- (pillar, self.information_type)
- for pillar in self.affected_pillars))
- existing_links = set([
- (apa.abstract_artifact, apa.policy)
- for apa in apasource.findByArtifact([artifact])])
- apasource.create(wanted_links - existing_links)
- apasource.delete(existing_links - wanted_links)
+ def _reconcileAccess(self):
+ reconcile_access_for_artifact(
+ self, self.information_type, self.affected_pillars)
def _attachments_query(self):
"""Helper for the attachments* properties."""
@@ -2758,7 +2735,7 @@
if params.milestone:
bug_task.transitionToMilestone(params.milestone, params.owner)
- bug.updateAccessPolicyArtifacts()
+ bug._reconcileAccess()
# Tell everyone.
if notify_event:
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2012-06-01 06:20:05 +0000
+++ lib/lp/bugs/model/bugtask.py 2012-06-04 09:50:27 +0000
@@ -41,7 +41,6 @@
import pytz
from sqlobject import (
ForeignKey,
- IntCol,
SQLObjectNotFound,
StringCol,
)
@@ -639,7 +638,7 @@
notify(ObjectDeletedEvent(self, who))
self.destroySelf()
del get_property_cache(bug).bugtasks
- self.bug.updateAccessPolicyArtifacts()
+ self.bug._reconcileAccess()
# When a task is deleted, we also delete it's BugNomination entry
# if there is one. Sadly, getNominationFor() can return None or
@@ -1175,7 +1174,7 @@
for name, value in new_key.iteritems():
setattr(self, name, value)
self.updateTargetNameCache()
- self.bug.updateAccessPolicyArtifacts()
+ self.bug._reconcileAccess()
# START TEMPORARY BIT FOR BUGTASK AUTOCONFIRM FEATURE FLAG.
# We also should see if we ought to auto-transition to the
@@ -1648,7 +1647,7 @@
bugtask.updateTargetNameCache()
if bugtask.conjoined_slave:
bugtask._syncFromConjoinedSlave()
- removeSecurityProxy(bug).updateAccessPolicyArtifacts()
+ removeSecurityProxy(bug)._reconcileAccess()
return tasks
def createTask(self, bug, owner, target, status=None, importance=None,
=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
--- lib/lp/bugs/model/tests/test_bug.py 2012-06-02 13:12:32 +0000
+++ lib/lp/bugs/model/tests/test_bug.py 2012-06-04 09:50:27 +0000
@@ -23,10 +23,7 @@
)
from lp.bugs.errors import BugCannotBePrivate
from lp.bugs.interfaces.bugnotification import IBugNotificationSet
-from lp.bugs.interfaces.bugtask import (
- BugTaskStatus,
- IBugTaskSet,
- )
+from lp.bugs.interfaces.bugtask import BugTaskStatus
from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
from lp.bugs.model.bug import (
BugNotification,
@@ -40,6 +37,7 @@
)
from lp.registry.enums import InformationType
from lp.registry.interfaces.person import PersonVisibility
+from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
from lp.testing import (
admin_logged_in,
feature_flags,
@@ -50,11 +48,7 @@
StormStatementRecorder,
TestCaseWithFactory,
)
-from lp.testing.dbtriggers import (
- disable_trigger,
- triggers_disabled,
- )
-from lp.testing.dbuser import dbuser
+from lp.testing.dbtriggers import triggers_disabled
from lp.testing.layers import DatabaseFunctionalLayer
from lp.testing.matchers import (
Equals,
@@ -63,13 +57,6 @@
)
-def get_policies_for_bug(bug):
- [artifact] = getUtility(IAccessArtifactSource).find([bug])
- return [
- apa.policy for apa in
- getUtility(IAccessPolicyArtifactSource).findByArtifact([artifact])]
-
-
LEGACY_ACCESS_TRIGGERS = [
('bug', 'bug_mirror_legacy_access_t'),
('bugtask', 'bugtask_mirror_legacy_access_t'),
@@ -854,9 +841,9 @@
bug.transitionToInformationType(
InformationType.USERDATA, bug.owner)
- [expected_policy] = getUtility(IAccessPolicySource).find(
+ [policy] = getUtility(IAccessPolicySource).find(
[(product, InformationType.USERDATA)])
- self.assertContentEqual([expected_policy], get_policies_for_bug(bug))
+ self.assertContentEqual([policy], get_policies_for_artifact(bug))
def test_private_to_public_information_type(self):
# A private bug transitioning to public has the correct information
@@ -916,6 +903,33 @@
self.assertEqual(
[reporter], [recipient.person for recipient in recipients])
+ def test__reconcileAccess_handles_all_targets(self):
+ # _reconcileAccess gets the pillar from any task
+ # type.
+ product = self.factory.makeProduct()
+ productseries = self.factory.makeProductSeries()
+ distro = self.factory.makeDistribution()
+ distroseries = self.factory.makeDistroSeries()
+ dsp = self.factory.makeDistributionSourcePackage()
+ sp = self.factory.makeSourcePackage()
+
+ targets = [product, productseries, distro, distroseries, dsp, sp]
+ pillars = [
+ product, productseries.product, distro, distroseries.distribution,
+ dsp.distribution, sp.distribution]
+
+ bug = self.factory.makeBug(
+ product=product, information_type=InformationType.USERDATA)
+ for target in targets[1:]:
+ self.factory.makeBugTask(bug, target=target)
+ [artifact] = getUtility(IAccessArtifactSource).ensure([bug])
+ getUtility(IAccessPolicyArtifactSource).deleteByArtifact([artifact])
+ removeSecurityProxy(bug)._reconcileAccess()
+ self.assertContentEqual(
+ getUtility(IAccessPolicySource).find(
+ (pillar, InformationType.USERDATA) for pillar in pillars),
+ get_policies_for_artifact(bug))
+
class TestBugPrivateAndSecurityRelatedUpdatesPrivateProject(
TestBugPrivateAndSecurityRelatedUpdatesMixin, TestCaseWithFactory):
@@ -1103,105 +1117,3 @@
duplicate_bug = self.factory.makeBug(owner=bug.owner)
duplicate_bug.markAsDuplicate(bug)
self.assertEqual(BugTaskStatus.NEW, bug.bugtasks[0].status)
-
-
-class TestBugUpdateAccessPolicyArtifacts(TestCaseWithFactory):
-
- layer = DatabaseFunctionalLayer
-
- def setUp(self):
- super(TestBugUpdateAccessPolicyArtifacts, self).setUp()
-
- # Disable the transitional triggers that the app code is to
- # replace.
- with dbuser('postgres'):
- for table, trigger in LEGACY_ACCESS_TRIGGERS:
- disable_trigger(table, trigger)
-
- def assertPoliciesForBug(self, policy_tuples, bug):
- self.assertContentEqual(
- getUtility(IAccessPolicySource).find(policy_tuples),
- get_policies_for_bug(bug))
-
- def test_creates_missing_accessartifact(self):
- # updateAccessPolicyArtifacts creates an AccessArtifact for a
- # private bug if there isn't one already.
- bug = self.factory.makeBug(information_type=InformationType.USERDATA)
- getUtility(IAccessArtifactSource).delete([bug])
-
- self.assertTrue(
- getUtility(IAccessArtifactSource).find([bug]).is_empty())
- removeSecurityProxy(bug).updateAccessPolicyArtifacts()
- self.assertFalse(
- getUtility(IAccessArtifactSource).find([bug]).is_empty())
-
- def test_removes_extra_accessartifact(self):
- # updateAccessPolicyArtifacts creates an AccessArtifact for a
- # private bug if there isn't one already.
- bug = self.factory.makeBug(information_type=InformationType.PUBLIC)
- getUtility(IAccessArtifactSource).ensure([bug])
-
- self.assertFalse(
- getUtility(IAccessArtifactSource).find([bug]).is_empty())
- removeSecurityProxy(bug).updateAccessPolicyArtifacts()
- self.assertTrue(
- getUtility(IAccessArtifactSource).find([bug]).is_empty())
-
- def test_adds_missing_accesspolicyartifacts(self):
- # updateAccessPolicyArtifacts adds missing links.
- product = self.factory.makeProduct()
- bug = self.factory.makeBug(
- product=product, information_type=InformationType.USERDATA)
- [artifact] = getUtility(IAccessArtifactSource).find([bug])
- getUtility(IAccessPolicyArtifactSource).deleteByArtifact([artifact])
-
- self.assertPoliciesForBug([], bug)
- removeSecurityProxy(bug).updateAccessPolicyArtifacts()
- self.assertPoliciesForBug([(product, InformationType.USERDATA)], bug)
-
- def test_removes_extra_accesspolicyartifacts(self):
- # updateAccessPolicyArtifacts removes excess links.
- product = self.factory.makeProduct()
- bug = self.factory.makeBug(
- product=product, information_type=InformationType.USERDATA)
-
- other_product = self.factory.makeProduct()
- [other_policy] = getUtility(IAccessPolicySource).find(
- [(other_product, InformationType.USERDATA)])
- [artifact] = getUtility(IAccessArtifactSource).find([bug])
- getUtility(IAccessPolicyArtifactSource).create(
- [(artifact, other_policy)])
-
- self.assertPoliciesForBug(
- [(product, InformationType.USERDATA),
- (other_product, InformationType.USERDATA)],
- bug)
- removeSecurityProxy(bug).updateAccessPolicyArtifacts()
- self.assertPoliciesForBug([(product, InformationType.USERDATA)], bug)
-
- def test_all_target_types_work(self):
- # updateAccessPolicyArtifacts gets the pillar from any task
- # type.
- product = self.factory.makeProduct()
- productseries = self.factory.makeProductSeries()
- distro = self.factory.makeDistribution()
- distroseries = self.factory.makeDistroSeries()
- dsp = self.factory.makeDistributionSourcePackage()
- sp = self.factory.makeSourcePackage()
-
- targets = [product, productseries, distro, distroseries, dsp, sp]
- pillars = [
- product, productseries.product, distro, distroseries.distribution,
- dsp.distribution, sp.distribution]
-
- bug = self.factory.makeBug(
- product=product, information_type=InformationType.USERDATA)
- for target in targets[1:]:
- self.factory.makeBugTask(bug, target=target)
- [artifact] = getUtility(IAccessArtifactSource).find([bug])
- getUtility(IAccessPolicyArtifactSource).deleteByArtifact([artifact])
-
- self.assertPoliciesForBug([], bug)
- removeSecurityProxy(bug).updateAccessPolicyArtifacts()
- self.assertPoliciesForBug(
- [(pillar, InformationType.USERDATA) for pillar in pillars], bug)
=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
--- lib/lp/bugs/model/tests/test_bugtask.py 2012-05-30 08:34:29 +0000
+++ lib/lp/bugs/model/tests/test_bugtask.py 2012-06-04 09:50:27 +0000
@@ -59,10 +59,7 @@
_build_status_clause,
_build_tag_search_clause,
)
-from lp.bugs.model.tests.test_bug import (
- get_policies_for_bug,
- LEGACY_ACCESS_TRIGGERS,
- )
+from lp.bugs.model.tests.test_bug import LEGACY_ACCESS_TRIGGERS
from lp.bugs.scripts.bugtasktargetnamecaches import (
BugTaskTargetNameCacheUpdater)
from lp.bugs.tests.bug import create_old_bug
@@ -90,6 +87,7 @@
from lp.registry.interfaces.projectgroup import IProjectGroupSet
from lp.registry.interfaces.sourcepackage import ISourcePackage
from lp.registry.model.sourcepackage import SourcePackage
+from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
from lp.services.database.sqlbase import (
convert_storm_clause_to_string,
flush_database_updates,
@@ -248,7 +246,8 @@
(new_product, InformationType.USERDATA),
(old_product, InformationType.USERDATA),
])
- self.assertContentEqual(expected_policies, get_policies_for_bug(bug))
+ self.assertContentEqual(
+ expected_policies, get_policies_for_artifact(bug))
class TestBugTaskCreationPackageComponent(TestCaseWithFactory):
@@ -2199,7 +2198,8 @@
(old_product, InformationType.USERDATA),
(new_product, InformationType.USERDATA),
])
- self.assertContentEqual(expected_policies, get_policies_for_bug(bug))
+ self.assertContentEqual(
+ expected_policies, get_policies_for_artifact(bug))
# There are also transitional triggers that do this. Disable
# them temporarily so we can be sure the application side works.
@@ -2210,7 +2210,8 @@
expected_policies = getUtility(IAccessPolicySource).find([
(old_product, InformationType.USERDATA),
])
- self.assertContentEqual(expected_policies, get_policies_for_bug(bug))
+ self.assertContentEqual(
+ expected_policies, get_policies_for_artifact(bug))
class TestStatusCountsForProductSeries(TestCaseWithFactory):
@@ -3287,7 +3288,8 @@
[expected_policy] = getUtility(IAccessPolicySource).find(
[(new_product, InformationType.USERDATA)])
- self.assertContentEqual([expected_policy], get_policies_for_bug(bug))
+ self.assertContentEqual(
+ [expected_policy], get_policies_for_artifact(bug))
def test_matching_sourcepackage_tasks_updated_when_name_changed(self):
# If the sourcepackagename is changed, it's changed on all tasks
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2012-05-25 01:48:31 +0000
+++ lib/lp/code/model/branch.py 2012-06-04 09:50:27 +0000
@@ -138,6 +138,7 @@
validate_person,
validate_public_person,
)
+from lp.registry.model.accesspolicy import reconcile_access_for_artifact
from lp.services.config import config
from lp.services.database.bulk import load_related
from lp.services.database.constants import (
@@ -194,6 +195,20 @@
def private(self):
return self.information_type in PRIVATE_INFORMATION_TYPES
+ def _reconcileAccess(self):
+ """Reconcile the branch's sharing information.
+
+ Takes the information_type and target and makes the related
+ AccessArtifact and AccessPolicyArtifacts match.
+ """
+ # We haven't yet quite worked out how distribution privacy
+ # works, so only work for products for now.
+ if self.product is not None:
+ pillars = [self.product]
+ else:
+ pillars = []
+ reconcile_access_for_artifact(self, self.information_type, pillars)
+
def setPrivate(self, private, user):
"""See `IBranch`."""
if private:
@@ -220,6 +235,7 @@
if not private and not policy.canBranchesBePublic():
raise BranchCannotBePublic()
self.information_type = information_type
+ self._reconcileAccess()
# Set the legacy values for now.
self.explicitly_private = private
# If this branch is private, then it is also transitively_private
@@ -312,6 +328,7 @@
# Person targets are always valid.
namespace = target.getNamespace(self.owner)
namespace.moveBranch(self, user, rename_if_necessary=True)
+ self._reconcileAccess()
@property
def namespace(self):
=== modified file 'lib/lp/code/model/branchnamespace.py'
--- lib/lp/code/model/branchnamespace.py 2012-05-04 04:52:24 +0000
+++ lib/lp/code/model/branchnamespace.py 2012-06-04 09:50:27 +0000
@@ -148,6 +148,8 @@
CodeReviewNotificationLevel.FULL,
registrant)
+ branch._reconcileAccess()
+
notify(ObjectCreatedEvent(branch))
return branch
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2012-05-31 03:54:13 +0000
+++ lib/lp/code/model/tests/test_branch.py 2012-06-04 09:50:27 +0000
@@ -111,9 +111,15 @@
from lp.codehosting.safe_open import BadUrl
from lp.codehosting.vfs.branchfs import get_real_branch_path
from lp.registry.enums import InformationType
+from lp.registry.interfaces.accesspolicy import (
+ IAccessArtifactSource,
+ IAccessPolicyArtifactSource,
+ IAccessPolicySource,
+ )
from lp.registry.interfaces.person import PersonVisibility
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.model.sourcepackage import SourcePackage
+from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
from lp.services.config import config
from lp.services.database.constants import UTC_NOW
from lp.services.database.lpstorm import IStore
@@ -126,6 +132,7 @@
from lp.services.propertycache import clear_property_cache
from lp.services.webapp.interfaces import IOpenLaunchBag
from lp.testing import (
+ admin_logged_in,
ANONYMOUS,
celebrity_logged_in,
launchpadlib_for,
@@ -2363,6 +2370,26 @@
self.assertTrue(branch.private)
self.assertEqual(InformationType.USERDATA, branch.information_type)
+ def test__reconcileAccess_for_product_branch(self):
+ # _reconcileAccess uses a product policy for a product branch.
+ branch = self.factory.makeBranch(
+ information_type=InformationType.USERDATA)
+ [artifact] = getUtility(IAccessArtifactSource).ensure([branch])
+ getUtility(IAccessPolicyArtifactSource).deleteByArtifact([artifact])
+ removeSecurityProxy(branch)._reconcileAccess()
+ self.assertContentEqual(
+ getUtility(IAccessPolicySource).find(
+ [(branch.product, InformationType.USERDATA)]),
+ get_policies_for_artifact(branch))
+
+ def test__reconcileAccess_for_distro_branch(self):
+ # Branch privacy isn't yet supported for distributions, so no
+ # AccessPolicyArtifact is created for a distro branch.
+ branch = self.factory.makePackageBranch(
+ information_type=InformationType.USERDATA)
+ removeSecurityProxy(branch)._reconcileAccess()
+ self.assertEqual([], get_policies_for_artifact(branch))
+
class TestBranchSetPrivate(TestCaseWithFactory):
"""Test IBranch.setPrivate."""
@@ -2471,6 +2498,19 @@
self.assertEqual(
InformationType.UNEMBARGOEDSECURITY, branch.information_type)
+ def test_transition_reconciles_access(self):
+ # transitionToStatus calls _reconcileAccess to make the sharing
+ # schema match the new value.
+ branch = self.factory.makeBranch(
+ information_type=InformationType.USERDATA)
+ with admin_logged_in():
+ branch.transitionToInformationType(
+ InformationType.EMBARGOEDSECURITY, branch.owner,
+ verify_policy=False)
+ self.assertEqual(
+ InformationType.EMBARGOEDSECURITY,
+ get_policies_for_artifact(branch)[0].type)
+
class TestBranchCommitsForDays(TestCaseWithFactory):
"""Tests for `Branch.commitsForDays`."""
@@ -2805,6 +2845,17 @@
branch.setTarget(user=branch.owner)
self.assertEqual(branch.owner, branch.target.context)
+ def test_reconciles_access(self):
+ # setTarget calls _reconcileAccess to make the sharing schema
+ # match the new target.
+ branch = self.factory.makeBranch(
+ information_type=InformationType.USERDATA)
+ new_product = self.factory.makeProduct()
+ with admin_logged_in():
+ branch.setTarget(user=branch.owner, project=new_product)
+ self.assertEqual(
+ new_product, get_policies_for_artifact(branch)[0].pillar)
+
class TestScheduleDiffUpdates(TestCaseWithFactory):
=== modified file 'lib/lp/registry/model/accesspolicy.py'
--- lib/lp/registry/model/accesspolicy.py 2012-06-01 05:52:47 +0000
+++ lib/lp/registry/model/accesspolicy.py 2012-06-04 09:50:27 +0000
@@ -9,6 +9,7 @@
'AccessPolicy',
'AccessPolicyArtifact',
'AccessPolicyGrant',
+ 'reconcile_access_for_artifact',
]
from collections import defaultdict
@@ -32,16 +33,19 @@
from lp.registry.enums import (
InformationType,
+ PUBLIC_INFORMATION_TYPES,
SharingPermission,
)
from lp.registry.interfaces.accesspolicy import (
IAccessArtifact,
+ IAccessArtifactSource,
IAccessArtifactGrant,
IAccessArtifactGrantSource,
IAccessPolicy,
IAccessPolicyArtifact,
IAccessPolicyArtifactSource,
IAccessPolicyGrant,
+ IAccessPolicySource,
)
from lp.registry.model.person import Person
from lp.services.database.bulk import create
@@ -51,6 +55,28 @@
from lp.services.database.stormbase import StormBase
+def reconcile_access_for_artifact(artifact, information_type, pillars):
+ if information_type in PUBLIC_INFORMATION_TYPES:
+ # If it's public we can delete all the access information.
+ # IAccessArtifactSource handles the cascade.
+ getUtility(IAccessArtifactSource).delete([artifact])
+ return
+ [abstract_artifact] = getUtility(IAccessArtifactSource).ensure([artifact])
+
+ # Now determine the existing and desired links, and make them
+ # match.
+ apasource = getUtility(IAccessPolicyArtifactSource)
+ wanted_links = set(
+ (abstract_artifact, policy) for policy in
+ getUtility(IAccessPolicySource).find(
+ (pillar, information_type) for pillar in pillars))
+ existing_links = set([
+ (apa.abstract_artifact, apa.policy)
+ for apa in apasource.findByArtifact([abstract_artifact])])
+ apasource.create(wanted_links - existing_links)
+ apasource.delete(existing_links - wanted_links)
+
+
class AccessArtifact(StormBase):
implements(IAccessArtifact)
=== modified file 'lib/lp/registry/tests/test_accesspolicy.py'
--- lib/lp/registry/tests/test_accesspolicy.py 2012-06-01 05:25:24 +0000
+++ lib/lp/registry/tests/test_accesspolicy.py 2012-06-04 09:50:27 +0000
@@ -24,6 +24,7 @@
IAccessPolicyGrantSource,
IAccessPolicySource,
)
+from lp.registry.model.accesspolicy import reconcile_access_for_artifact
from lp.registry.model.person import Person
from lp.services.database.lpstorm import IStore
from lp.testing import TestCaseWithFactory
@@ -31,6 +32,13 @@
from lp.testing.matchers import Provides
+def get_policies_for_artifact(concrete_artifact):
+ [artifact] = getUtility(IAccessArtifactSource).find([concrete_artifact])
+ return [
+ apa.policy for apa in
+ getUtility(IAccessPolicyArtifactSource).findByArtifact([artifact])]
+
+
class TestAccessPolicy(TestCaseWithFactory):
layer = DatabaseFunctionalLayer
@@ -620,3 +628,63 @@
self.factory.makeAccessPolicyArtifact(artifact=artifact, policy=policy)
self.assertContentEqual(
[artifact], apgfs.findArtifactsByGrantee(grantee, [policy]))
+
+
+class TestReconcileAccessPolicyArtifacts(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def assertPoliciesForBug(self, policy_tuples, bug):
+ self.assertContentEqual(
+ getUtility(IAccessPolicySource).find(policy_tuples),
+ get_policies_for_artifact(bug))
+
+ def test_creates_missing_accessartifact(self):
+ # reconcile_access_for_artifact creates an AccessArtifact for a
+ # private artifact if there isn't one already.
+ bug = self.factory.makeBug()
+
+ self.assertTrue(
+ getUtility(IAccessArtifactSource).find([bug]).is_empty())
+ reconcile_access_for_artifact(bug, InformationType.USERDATA, [])
+ self.assertFalse(
+ getUtility(IAccessArtifactSource).find([bug]).is_empty())
+
+ def test_removes_extra_accessartifact(self):
+ # reconcile_access_for_artifact removes an AccessArtifact for a
+ # public artifact if there's one left over.
+ bug = self.factory.makeBug()
+ reconcile_access_for_artifact(bug, InformationType.USERDATA, [])
+
+ self.assertFalse(
+ getUtility(IAccessArtifactSource).find([bug]).is_empty())
+ reconcile_access_for_artifact(bug, InformationType.PUBLIC, [])
+ self.assertTrue(
+ getUtility(IAccessArtifactSource).find([bug]).is_empty())
+
+ def test_adds_missing_accesspolicyartifacts(self):
+ # reconcile_access_for_artifact adds missing links.
+ product = self.factory.makeProduct()
+ bug = self.factory.makeBug(product=product)
+ reconcile_access_for_artifact(bug, InformationType.USERDATA, [])
+
+ self.assertPoliciesForBug([], bug)
+ reconcile_access_for_artifact(
+ bug, InformationType.USERDATA, [product])
+ self.assertPoliciesForBug([(product, InformationType.USERDATA)], bug)
+
+ def test_removes_extra_accesspolicyartifacts(self):
+ # reconcile_access_for_artifact removes excess links.
+ bug = self.factory.makeBug()
+ product = self.factory.makeProduct()
+ other_product = self.factory.makeProduct()
+ reconcile_access_for_artifact(
+ bug, InformationType.USERDATA, [product, other_product])
+
+ self.assertPoliciesForBug(
+ [(product, InformationType.USERDATA),
+ (other_product, InformationType.USERDATA)],
+ bug)
+ reconcile_access_for_artifact(
+ bug, InformationType.USERDATA, [product])
+ self.assertPoliciesForBug([(product, InformationType.USERDATA)], bug)
Follow ups