launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06301
[Merge] lp:~wallyworld/launchpad/private-by-default-ui-885503 into lp:launchpad
Ian Booth has proposed merging lp:~wallyworld/launchpad/private-by-default-ui-885503 into lp:launchpad with lp:~wallyworld/launchpad/drop-private-bugs-need-contact-constraint as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #885503 in Launchpad itself: "Its not obvious on the UI how to choose to have all bugs reported against your project marked private by default."
https://bugs.launchpad.net/launchpad/+bug/885503
For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/private-by-default-ui-885503/+merge/92273
== Implementation ==
The bug report mentions that the private_bugs field is on the Configure Bug Tracker page but it is on the Product +admin page. All the fields on the +admin page are protected by the launchpad.Moderate permission. But we want to allow project maintainers with commercial subscriptions to edit the field. So...
I copied the field to the Configure Bug Tracker page (the same page where Bug Supervisor etc is set). The field needs to be set by both ~registry and ~admin (launchpad.Moderate) and commercial subscribers (these have launchpad.Edit). There are 2 permissions involved so the best approach is to use a mutator and put the permission checks in a new method on Product privateBugsAllowed(). NB I had to update the recently added hasCurrentCommercialSubscription() method to take an optional product parameter.
A new exception CommercialSubscribersOnly is raised in the mutator so that unauthorised people cannot turn on private bugs using the API.
Summary:
Product+admin and Product+review-license pages can still be used as before by ~registry and ~admin to set private_bugs = on
Product+configure-bugtracker can be used by commercial subscribers to set private_bugs = on
Any user who can edit fields on the +configure-bugtracker page can turn private_bugs = off
I removed the unnecessary validation check which complained if the bug supervisor is not set when turning on the private_bugs field. I discovered there is also a db constraint on the product table so a pre-requisite branch needs to remove this constraint during FDT.
== Tests ==
Add new test to TestPerson for the new hasCurrentCommercialSubscription behaviour:
- test_has_current_commercial_subscription_for_product
Add new tests to TestProduct for the privateBugsAllowed() method. Also add tests for setPrivateBug()
Add new tests to TestProductBugConfigurationView for setting private_bugs
- test_commercial_subscriber_can_turn_on_private_bugs
- test_unauthorised_cannot_turn_on_private_bugs
- test_anyone_can_turn_off_private_bugs
== Lint ==
Linting changed files:
lib/lp/bugs/browser/bugtarget.py
lib/lp/bugs/browser/tests/test_bugtarget_configure.py
lib/lp/registry/configure.zcml
lib/lp/registry/errors.py
lib/lp/registry/browser/product.py
lib/lp/registry/browser/tests/product-views.txt
lib/lp/registry/interfaces/person.py
lib/lp/registry/interfaces/product.py
lib/lp/registry/model/person.py
lib/lp/registry/model/product.py
lib/lp/registry/stories/product/xx-product-with-private-defaults.txt
lib/lp/registry/tests/test_errors.py
lib/lp/registry/tests/test_person.py
lib/lp/registry/tests/test_product.py
--
https://code.launchpad.net/~wallyworld/launchpad/private-by-default-ui-885503/+merge/92273
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/private-by-default-ui-885503 into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py 2012-01-09 11:36:12 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2012-02-09 13:45:41 +0000
@@ -113,7 +113,10 @@
from lp.bugs.publisher import BugsLayer
from lp.bugs.utilities.filebugdataparser import FileBugData
from lp.hardwaredb.interfaces.hwdb import IHWSubmissionSet
-from lp.registry.browser.product import ProductConfigureBase
+from lp.registry.browser.product import (
+ ProductConfigureBase,
+ ProductPrivateBugsMixin,
+ )
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
@@ -151,6 +154,8 @@
bug_supervisor = copy_field(
IHasBugSupervisor['bug_supervisor'], readonly=False)
security_contact = copy_field(IHasSecurityContact['security_contact'])
+ private_bugs = copy_field(
+ IProduct['private_bugs'], readonly=False)
official_malone = copy_field(ILaunchpadUsage['official_malone'])
enable_bug_expiration = copy_field(
ILaunchpadUsage['enable_bug_expiration'])
@@ -171,7 +176,9 @@
return product
-class ProductConfigureBugTrackerView(BugRoleMixin, ProductConfigureBase):
+class ProductConfigureBugTrackerView(BugRoleMixin,
+ ProductPrivateBugsMixin,
+ ProductConfigureBase):
"""View class to configure the bug tracker for a project."""
label = "Configure bug tracker"
@@ -192,6 +199,7 @@
"bug_reporting_guidelines",
"bug_reported_acknowledgement",
"enable_bugfiling_duplicate_search",
+ "private_bugs"
]
if check_permission("launchpad.Edit", self.context):
field_names.extend(["bug_supervisor", "security_contact"])
@@ -200,6 +208,7 @@
def validate(self, data):
"""Constrain bug expiration to Launchpad Bugs tracker."""
+ super(ProductConfigureBugTrackerView, self).validate(data)
if check_permission("launchpad.Edit", self.context):
self.validateBugSupervisor(data)
self.validateSecurityContact(data)
=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_configure.py'
--- lib/lp/bugs/browser/tests/test_bugtarget_configure.py 2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/browser/tests/test_bugtarget_configure.py 2012-02-09 13:45:41 +0000
@@ -39,6 +39,7 @@
'field.bug_reporting_guidelines': 'guidelines',
'field.bug_reported_acknowledgement': 'acknowledgement message',
'field.enable_bugfiling_duplicate_search': False,
+ 'field.private_bugs': 'off',
'field.actions.change': 'Change',
}
@@ -50,8 +51,8 @@
fields = [
'bugtracker', 'enable_bug_expiration', 'remote_product',
'bug_reporting_guidelines', 'bug_reported_acknowledgement',
- 'enable_bugfiling_duplicate_search', 'bug_supervisor',
- 'security_contact']
+ 'enable_bugfiling_duplicate_search', 'private_bugs',
+ 'bug_supervisor', 'security_contact']
self.assertEqual(fields, view.field_names)
self.assertEqual('http://launchpad.dev/boing', view.next_url)
self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
@@ -65,7 +66,7 @@
fields = [
'bugtracker', 'enable_bug_expiration', 'remote_product',
'bug_reporting_guidelines', 'bug_reported_acknowledgement',
- 'enable_bugfiling_duplicate_search']
+ 'enable_bugfiling_duplicate_search', 'private_bugs']
self.assertEqual(fields, view.field_names)
self.assertEqual('http://launchpad.dev/boing', view.next_url)
self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
@@ -186,3 +187,35 @@
self.assertEqual([], view.errors)
self.assertEqual(
'new guidelines', self.product.bug_reporting_guidelines)
+
+ def test_commercial_subscriber_can_turn_on_private_bugs(self):
+ # Verify commercial subscribers can set private_bugs to on.
+ form = self._makeForm()
+ self.factory.makeCommercialSubscription(self.product)
+ form['field.private_bugs'] = 'on'
+ login_person(self.product.owner)
+ view = create_initialized_view(
+ self.product, name='+configure-bugtracker', form=form)
+ self.assertEqual([], view.errors)
+ self.assertTrue(self.product.private_bugs)
+
+ def test_unauthorised_cannot_turn_on_private_bugs(self):
+ # Verify unauthorised users cannot set private_bugs to on.
+ form = self._makeForm()
+ form['field.private_bugs'] = 'on'
+ view = create_initialized_view(
+ self.product, name='+configure-bugtracker', form=form)
+ self.assertEqual(
+ [u'A valid commercial subscription is required to turn on '
+ u'default private bugs.'],
+ view.errors)
+
+ def test_anyone_can_turn_off_private_bugs(self):
+ # Verify any user who can edit the product can set private_bugs off.
+ registry_expert = self.factory.makeRegistryExpert()
+ self.product.setPrivateBugs(registry_expert, True)
+ form = self._makeForm()
+ view = create_initialized_view(
+ self.product, name='+configure-bugtracker', form=form)
+ self.assertEqual([], view.errors)
+ self.assertFalse(self.product.private_bugs)
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2012-02-01 15:26:32 +0000
+++ lib/lp/registry/browser/product.py 2012-02-09 13:45:41 +0000
@@ -26,6 +26,7 @@
'ProductOverviewMenu',
'ProductPackagesView',
'ProductPackagesPortletView',
+ 'ProductPrivateBugsMixin',
'ProductPurchaseSubscriptionView',
'ProductRdfView',
'ProductReviewLicenseView',
@@ -1524,6 +1525,34 @@
usage_fieldname = 'answers_usage'
+class ProductPrivateBugsMixin():
+ """A mixin for setting the product private_bugs field."""
+ def setUpFields(self):
+ # private_bugs is readonly since we are using a mutator but we need
+ # to edit it on the form.
+ super(ProductPrivateBugsMixin, self).setUpFields()
+ self.form_fields['private_bugs'].field.readonly = False
+
+ def validate(self, data):
+ super(ProductPrivateBugsMixin, self).validate(data)
+ if (data.get('private_bugs', False) and
+ not self.context.privateBugsAllowed(self.user)):
+ self.setFieldError(
+ 'private_bugs',
+ 'A valid commercial subscription is required to turn on '
+ 'default private bugs.')
+
+ def updateContextFromData(self, data, context=None, notify_modified=True):
+ # private_bugs uses a mutator to check permissions, so it needs to
+ # be handled separately.
+ if (data.get('private_bugs') is not None
+ and data['private_bugs'] != self.context.private_bugs):
+ self.context.setPrivateBugs(self.user, data['private_bugs'])
+ del data['private_bugs']
+ parent = super(ProductPrivateBugsMixin, self)
+ return parent.updateContextFromData(data, context, notify_modified)
+
+
class ProductEditView(ProductLicenseMixin, LaunchpadEditFormView):
"""View class that lets you edit a Product object."""
@@ -1602,15 +1631,6 @@
class ProductValidationMixin:
- def validate_private_bugs(self, data):
- """Perform validation for the private bugs setting."""
- if data.get('private_bugs') and self.context.bug_supervisor is None:
- self.setFieldError('private_bugs',
- structured(
- 'Set a <a href="%s/+bugsupervisor">bug supervisor</a> '
- 'for this project first.',
- canonical_url(self.context, rootsite="bugs")))
-
def validate_deactivation(self, data):
"""Verify whether a product can be safely deactivated."""
if data['active'] == False and self.context.active == True:
@@ -1623,7 +1643,8 @@
canonical_url(self.context, view_name='+packages')))
-class ProductAdminView(ProductEditView, ProductValidationMixin):
+class ProductAdminView(ProductEditView, ProductValidationMixin,
+ ProductPrivateBugsMixin):
"""View for $project/+admin"""
label = "Administer project details"
default_field_names = [
@@ -1691,7 +1712,7 @@
def validate(self, data):
"""See `LaunchpadFormView`."""
- self.validate_private_bugs(data)
+ super(ProductAdminView, self).validate(data)
self.validate_deactivation(data)
@property
@@ -1701,7 +1722,8 @@
class ProductReviewLicenseView(ReturnToReferrerMixin,
- ProductEditView, ProductValidationMixin):
+ ProductEditView, ProductValidationMixin,
+ ProductPrivateBugsMixin):
"""A view to review a project and change project privileges."""
label = "Review project"
field_names = [
@@ -1720,6 +1742,7 @@
def validate(self, data):
"""See `LaunchpadFormView`."""
+ super(ProductReviewLicenseView, self).validate(data)
# A project can only be approved if it has OTHER_OPEN_SOURCE as one of
# its licenses and not OTHER_PROPRIETARY.
licenses = self.context.licenses
@@ -1737,9 +1760,6 @@
# approved.
pass
- # Private bugs can only be enabled if the product has a bug
- # supervisor.
- self.validate_private_bugs(data)
self.validate_deactivation(data)
=== modified file 'lib/lp/registry/browser/tests/product-views.txt'
--- lib/lp/registry/browser/tests/product-views.txt 2011-12-24 17:49:30 +0000
+++ lib/lp/registry/browser/tests/product-views.txt 2012-02-09 13:45:41 +0000
@@ -208,34 +208,6 @@
>>> view = create_initialized_view(
... firefox, name='+review-license', form=form)
-He encounters an error, though, as the project does not have a bug
-supervisor set, which is required for turning on private bugs.
-
- >>> from lp.testing.pages import extract_text
- >>> for error in view.errors:
- ... print extract_text(error)
- Set a bug supervisor for this project first.
-
- >>> firefox.bug_supervisor is None
- True
-
-After setting the bug supervisor the project can have private bugs enabled.
-
- >>> login('mark@xxxxxxxxxxx')
- >>> firefox.setBugSupervisor(cprov, mark)
- >>> login('commercial-member@xxxxxxxxxxxxx')
-
- >>> firefox.private_bugs
- False
-
- >>> form = {
- ... 'field.active': 'on',
- ... 'field.private_bugs': 'on',
- ... 'field.reviewer_whiteboard': 'Reinstated old project',
- ... 'field.actions.change': 'Change',
- ... }
- >>> view = create_initialized_view(
- ... firefox, name='+review-license', form=form)
>>> view.errors
[]
>>> firefox.active
@@ -250,7 +222,7 @@
>>> from lp.registry.interfaces.product import License
- >>> firefox.private_bugs = False
+ >>> firefox.setPrivateBugs(commercial_member, False)
>>> login('test@xxxxxxxxxxxxx')
>>> firefox.licenses = [License.OTHER_PROPRIETARY]
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2012-02-08 06:00:46 +0000
+++ lib/lp/registry/configure.zcml 2012-02-09 13:45:41 +0000
@@ -1273,7 +1273,7 @@
project_reviewed
license_approved"
set_schema="lp.registry.interfaces.product.IProductModerateRestricted"
- set_attributes="active private_bugs "/>
+ set_attributes="active"/>
<!-- IHasAliases -->
=== modified file 'lib/lp/registry/errors.py'
--- lib/lp/registry/errors.py 2011-11-09 01:02:03 +0000
+++ lib/lp/registry/errors.py 2012-02-09 13:45:41 +0000
@@ -6,6 +6,7 @@
'DistroSeriesDifferenceError',
'NotADerivedSeriesError',
'CannotTransitionToCountryMirror',
+ 'CommercialSubscribersOnly',
'CountryMirrorAlreadySet',
'DeleteSubscriptionError',
'InvalidName',
@@ -67,6 +68,15 @@
"""
+@error_status(httplib.UNAUTHORIZED)
+class CommercialSubscribersOnly(Unauthorized):
+ """Feature is only available to current commercial subscribers.
+
+ Raised when a user tries to invoke an operation that is only available to
+ current commercial subscribers and they don't have an active subscription.
+ """
+
+
class NoSuchSourcePackageName(NameLookupFailed):
"""Raised when we can't find a particular sourcepackagename."""
_message_prefix = "No such source package"
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2012-02-03 11:23:30 +0000
+++ lib/lp/registry/interfaces/person.py 2012-02-09 13:45:41 +0000
@@ -1243,7 +1243,7 @@
:return: list
"""
- def hasCurrentCommercialSubscription():
+ def hasCurrentCommercialSubscription(product=None):
"""Return if the user has a current commercial subscription."""
def assignKarma(action_name, product=None, distribution=None,
=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py 2012-01-23 20:12:30 +0000
+++ lib/lp/registry/interfaces/product.py 2012-02-09 13:45:41 +0000
@@ -39,7 +39,10 @@
export_factory_operation,
export_operation_as,
export_read_operation,
+ export_write_operation,
exported,
+ mutator_for,
+ operation_for_version,
operation_parameters,
operation_returns_collection_of,
operation_returns_entry,
@@ -51,6 +54,7 @@
Reference,
ReferenceChoice,
)
+from lazr.restful.interface import copy_field
from zope.interface import (
Attribute,
Interface,
@@ -613,7 +617,7 @@
description=_("Whether or not this project's attributes are "
"updated automatically."))
- private_bugs = exported(Bool(title=_('Private bugs'),
+ private_bugs = exported(Bool(title=_('Private bugs'), readonly=True,
description=_(
"Whether or not bugs reported into this project "
"are private by default.")))
@@ -740,6 +744,21 @@
packagings = Attribute(_("All the packagings for the project."))
+ def privateBugsAllowed(user):
+ """Is the user allowed to turn on private bugs.
+
+ Generally, this is restricted to ~registry or ~admin or product
+ maintainers with active commercial subscriptions.
+ """
+
+ @mutator_for(private_bugs)
+ @call_with(user=REQUEST_USER)
+ @operation_parameters(private_bugs=copy_field(private_bugs))
+ @export_write_operation()
+ @operation_for_version("devel")
+ def setPrivateBugs(user, private_bugs):
+ """Mutator for private_bugs that checks entitlement."""
+
def getVersionSortedSeries(statuses=None, filter_statuses=None):
"""Return all the series sorted by the name field as a version.
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2012-02-03 11:23:30 +0000
+++ lib/lp/registry/model/person.py 2012-02-09 13:45:41 +0000
@@ -1230,7 +1230,7 @@
(voucher.voucher_id, voucher.status))
return vouchers
- def hasCurrentCommercialSubscription(self):
+ def hasCurrentCommercialSubscription(self, product=None):
"""See `IPerson`."""
# Circular imports.
from lp.registry.model.commercialsubscription import (
@@ -1239,6 +1239,10 @@
from lp.registry.model.person import Person
from lp.registry.model.product import Product
from lp.registry.model.teammembership import TeamParticipation
+
+ filter = [Person.id == self.id]
+ if product:
+ filter.append(Product.id == product.id)
person = Store.of(self).using(
Person,
Join(
@@ -1253,7 +1257,7 @@
Person,
CommercialSubscription.date_expires > datetime.now(
pytz.UTC),
- Person.id == self.id)
+ *filter)
return not person.is_empty()
def iterTopProjectsContributedTo(self, limit=10):
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2012-02-08 06:00:46 +0000
+++ lib/lp/registry/model/product.py 2012-02-09 13:45:41 +0000
@@ -3,6 +3,8 @@
# pylint: disable-msg=E0611,W0212
"""Database classes including and related to Product."""
+from lp.registry.errors import CommercialSubscribersOnly
+from lp.services.webapp.authorization import check_permission
__metaclass__ = type
__all__ = [
@@ -508,6 +510,22 @@
notNull=True, default=False,
storm_validator=_validate_license_approved)
+ def privateBugsAllowed(self, user):
+ """See `IProductPublic`."""
+ if user is None:
+ return False
+ return (
+ check_permission('launchpad.Moderate', self) or
+ user.hasCurrentCommercialSubscription(self))
+
+ def setPrivateBugs(self, user, private_bugs):
+ """ See `IProductEditRestricted`."""
+ if private_bugs and not self.privateBugsAllowed(user):
+ raise CommercialSubscribersOnly(
+ 'A valid commercial subscription is required to turn on '
+ 'default private bugs.')
+ self.private_bugs = private_bugs
+
@cachedproperty
def commercial_subscription(self):
return CommercialSubscription.selectOneBy(product=self)
=== modified file 'lib/lp/registry/stories/product/xx-product-with-private-defaults.txt'
--- lib/lp/registry/stories/product/xx-product-with-private-defaults.txt 2011-12-24 15:18:32 +0000
+++ lib/lp/registry/stories/product/xx-product-with-private-defaults.txt 2012-02-09 13:45:41 +0000
@@ -19,19 +19,6 @@
>>> admin_browser.getControl(name="field.private_bugs").value = True
>>> admin_browser.getControl("Change").click()
-However, the product's bug supervisor must be set, and if it is not, as is
-the case with Firefox, the user will get an error:
-
- >>> admin_browser.open("http://launchpad.dev/firefox/+admin")
- >>> admin_browser.getControl(name="field.private_bugs").value = True
- >>> admin_browser.getControl("Change").click()
- >>> admin_browser.url
- 'http://launchpad.dev/firefox/+admin'
- >>> for tag in find_tags_by_class(admin_browser.contents, "message"):
- ... print tag.renderContents()
- There is 1 error.
- <BLANKLINE>
- Set a <a href="...+bugsupervisor">bug supervisor</a> for this project first.
Filing a new bug
----------------
=== modified file 'lib/lp/registry/tests/test_errors.py'
--- lib/lp/registry/tests/test_errors.py 2012-01-01 02:58:52 +0000
+++ lib/lp/registry/tests/test_errors.py 2012-02-09 13:45:41 +0000
@@ -16,6 +16,7 @@
from lp.registry.errors import (
CannotTransitionToCountryMirror,
+ CommercialSubscribersOnly,
DeleteSubscriptionError,
DistroSeriesDifferenceError,
JoinNotAllowed,
@@ -90,3 +91,7 @@
def test_NameAlreadyTaken_bad_request(self):
error_view = create_webservice_error_view(NameAlreadyTaken())
self.assertEqual(CONFLICT, error_view.status)
+
+ def test_CommercialSubscribersOnly_bad_request(self):
+ error_view = create_webservice_error_view(CommercialSubscribersOnly())
+ self.assertEqual(UNAUTHORIZED, error_view.status)
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2012-02-03 11:23:30 +0000
+++ lib/lp/registry/tests/test_person.py 2012-02-09 13:45:41 +0000
@@ -538,13 +538,23 @@
self.assertFalse(person.isAnySecurityContact())
def test_has_current_commercial_subscription(self):
- # IPerson.hasCurrentCommercialSubscription() checks for one.
+ # IPerson.hasCurrentCommercialSubscription() checks for one.
team = self.factory.makeTeam(
subscription_policy=TeamSubscriptionPolicy.MODERATED)
product = self.factory.makeProduct(owner=team)
self.factory.makeCommercialSubscription(product)
self.assertTrue(team.teamowner.hasCurrentCommercialSubscription())
+ def test_has_current_commercial_subscription_for_product(self):
+ # IPerson.hasCurrentCommercialSubscription() checks for a commercial
+ # subscription for a particular product.
+ team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
+ product = self.factory.makeProduct(owner=team)
+ self.factory.makeCommercialSubscription(product)
+ self.assertTrue(
+ team.teamowner.hasCurrentCommercialSubscription(product))
+
def test_does_not_have_current_commercial_subscription(self):
# IPerson.hasCurrentCommercialSubscription() is false if it has
# expired.
=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py 2012-02-08 06:00:46 +0000
+++ lib/lp/registry/tests/test_product.py 2012-02-09 13:45:41 +0000
@@ -22,7 +22,15 @@
)
from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
+<<<<<<< TREE
from lp.registry.errors import OpenTeamLinkageError
+=======
+from lp.bugs.interfaces.bugtarget import IHasBugHeat
+from lp.registry.errors import (
+ CommercialSubscribersOnly,
+ OpenTeamLinkageError,
+ )
+>>>>>>> MERGE-SOURCE
from lp.registry.interfaces.oopsreferences import IHasOOPSReferences
from lp.registry.interfaces.person import (
CLOSED_TEAM_POLICY,
@@ -258,6 +266,39 @@
closed_team = self.factory.makeTeam(subscription_policy=policy)
self.factory.makeProduct(security_contact=closed_team)
+ def test_private_bugs_not_allowed_for_anonymous(self):
+ product = self.factory.makeProduct()
+ self.assertFalse(product.privateBugsAllowed(None))
+
+ def test_private_bugs_not_allowed_for_unauthorised(self):
+ product = self.factory.makeProduct()
+ someone = self.factory.makePerson()
+ self.assertFalse(product.privateBugsAllowed(someone))
+
+ def test_private_bugs_allowed_for_moderators(self):
+ product = self.factory.makeProduct()
+ registry_expert = self.factory.makeRegistryExpert()
+ self.assertTrue(product.privateBugsAllowed(registry_expert))
+
+ def test_private_bugs_allowed_for_commercial_subscribers(self):
+ product = self.factory.makeProduct()
+ self.factory.makeCommercialSubscription(product)
+ self.assertTrue(product.privateBugsAllowed(product.owner))
+
+ def test_unauthorised_set_private_bugs_raises(self):
+ # Test Product.setPrivateBugs raises an error if user unauthorised.
+ product = self.factory.makeProduct()
+ self.assertRaises(
+ CommercialSubscribersOnly,
+ product.setPrivateBugs, product.owner, True)
+
+ def test_set_private_bugs(self):
+ # Test Product.setPrivateBugs()
+ product = self.factory.makeProduct()
+ self.factory.makeCommercialSubscription(product)
+ product.setPrivateBugs(product.owner, True)
+ self.assertTrue(product.private_bugs)
+
class TestProductFiles(TestCase):
"""Tests for downloadable product files."""