launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #07177
[Merge] lp:~sinzui/launchpad/project-notify-5 into lp:launchpad
Curtis Hovey has proposed merging lp:~sinzui/launchpad/project-notify-5 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~sinzui/launchpad/project-notify-5/+merge/102734
Pre-implementation: abentley, jcsackett
Lp needs to send 30-day and 7-day commercial subscription expiration
notices, and it needs to send a message after expiration. The job that
deals with after expiration needs to deactivate the commercial features.
I hoped to also provide the mechanism to create the job's but this branch
is large and very old.
--------------------------------------------------------------------
RULES
* Create a job that sends an expired commercial subscription email
that also handles commercial feature deactivations:
* When the project license is proprietary, deactivate the project.
* When the project license is open source, deactivate private bugs
and branches.
* Do not make anything public...the information remains private,
but new private information cannot be made.
* Create a job that sends an commercial subscription expiration email
notice 7 days before the expiration date.
* Create a job that sends an commercial subscription expiration email
notice 30 days before the expiration date.
QA
None. My next branch will create a mechanism that created the 30, 7, and -1
day job so that we can test them. This also allows us to put the draft
emails in place while Dan provides the final draft.
LINT
lib/lp/registry/emailtemplates/product-commercial-subscription-expiration.txt
lib/lp/registry/emailtemplates/product-commercial-subscription-expired-open-source.txt
lib/lp/registry/emailtemplates/product-commercial-subscription-expired-proprietary.txt
lib/lp/registry/interfaces/productjob.py
lib/lp/registry/model/productjob.py
lib/lp/registry/tests/test_productjob.py
lib/lp/testing/factory.py
TEST
./bin/test -vvc --layer=Database lp.registry.tests.test_productjob
IMPLEMENTATION
Created three email templates for the conditions we recognise. These are
drafts. I will ask Dan to revise them.
lib/lp/registry/emailtemplates/product-commercial-subscription-expiration.txt
lib/lp/registry/emailtemplates/product-commercial-subscription-expired-open-source.txt
lib/lp/registry/emailtemplates/product-commercial-subscription-expired-proprietary.txt
Created three kinds of emails for 30 day, 7 day, and -1 day expiration
notifications. Commercial features are deactivated by the same job that sends
the -1 day expiration. I did not want to redefine CommercialExpiredJob's
email_template_name, I had planned to create a single email template for the
case, but for the sake of Dan and future editors, I decided not to inject
whole paragraphs to describe what changed.
lib/lp/registry/interfaces/productjob.py
lib/lp/registry/model/productjob.py
lib/lp/registry/tests/test_productjob.py
Fixed the factory which permitted me to create multiple commercial
subscriptions for a product and caused Storm to raise an exception.
lib/lp/testing/factory.py
--
https://code.launchpad.net/~sinzui/launchpad/project-notify-5/+merge/102734
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/project-notify-5 into lp:launchpad.
=== added file 'lib/lp/registry/emailtemplates/product-commercial-subscription-expiration.txt'
--- lib/lp/registry/emailtemplates/product-commercial-subscription-expiration.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/emailtemplates/product-commercial-subscription-expiration.txt 2012-04-19 18:03:32 +0000
@@ -0,0 +1,47 @@
+Hello %(user_displayname)s,
+
+The commercial subscription for project '%(product_name)s' in
+Launchpad will expire soon.
+%(commercial_use_expiration)s
+
+You can renew the commercial subscription which costs
+US$250/year/project. Follow the instructions presented on your
+project overview page to purchase a subscription voucher.
+%(product_url)s
+
+A commercial subscription allows you to host your commercial project
+on Launchpad in the same way as any other project. Project's with a
+commercial subscription may have private-by-default bugs, and you
+may also request the setup of private code hosting.
+
+As the maintainer of a project with a commercial subscription, you
+may create private teams with private mailing lists and private package
+archives. Find out more about this here:
+https://help.launchpad.net/CommercialHosting
+
+If '%(product_name)s' possessed an Other/Proprietary license at the time
+of expiration, the project will be deactivated. Otherwise, the
+commercial features will be deactivated. Things that are private will
+remain private, but no one will be able to create new things that are
+private.
+
+Launchpad is a collaboration site, free to use for projects with an
+approved open source license. When you registered your project, you'd
+have seen a list of licenses presented. These are the licences we
+automatically recognise.
+
+If you have a different licence to one on the approved list, it must
+follow the guidelines we list on the following page in order to be
+approved: https://help.launchpad.net/Legal/ProjectLicensing
+
+Want to know more?
+Further information is on our FAQ "Can closed-source or proprietary
+projects use Launchpad?" The link is here:
+https://answers.launchpad.net/launchpad/+faq/208
+
+If the license for your project needs to be corrected, you can do this
+by following the 'Change Details' link on your project's overview page.
+
+Thanks,
+
+The Launchpad team.
=== added file 'lib/lp/registry/emailtemplates/product-commercial-subscription-expired-open-source.txt'
--- lib/lp/registry/emailtemplates/product-commercial-subscription-expired-open-source.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/emailtemplates/product-commercial-subscription-expired-open-source.txt 2012-04-19 18:03:32 +0000
@@ -0,0 +1,40 @@
+Hello %(user_displayname)s,
+
+The commercial subscription for project '%(product_name)s' in
+Launchpad has expired.
+%(commercial_use_expiration)s
+
+Commercial features were deactivated. Things that are private will
+remain private. New bugs and branches are public by default! Private
+branches cannot be linked to project series.
+
+A commercial subscription allows you to host your commercial project
+on Launchpad in the same way as any other project. Project's with a
+commercial subscription may have private-by-default bugs, and you
+may also request the setup of private code hosting.
+
+As the maintainer of a project with a commercial subscription, you
+may create private teams with private mailing lists and private package
+archives. Find out more about this here:
+https://help.launchpad.net/CommercialHosting
+
+Launchpad is a collaboration site, free to use for projects with an
+approved open source license. When you registered your project, you'd
+have seen a list of licenses presented. These are the licences we
+automatically recognise.
+
+If you have a different licence to one on the approved list, it must
+follow the guidelines we list on the following page in order to be
+approved: https://help.launchpad.net/Legal/ProjectLicensing
+
+Want to know more?
+Further information is on our FAQ "Can closed-source or proprietary
+projects use Launchpad?" The link is here:
+https://answers.launchpad.net/launchpad/+faq/208
+
+If the license for your project needs to be corrected, you can do this
+by following the 'Change Details' link on your project's overview page.
+
+Thanks,
+
+The Launchpad team.
=== added file 'lib/lp/registry/emailtemplates/product-commercial-subscription-expired-proprietary.txt'
--- lib/lp/registry/emailtemplates/product-commercial-subscription-expired-proprietary.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/emailtemplates/product-commercial-subscription-expired-proprietary.txt 2012-04-19 18:03:32 +0000
@@ -0,0 +1,41 @@
+Hello %(user_displayname)s,
+
+The commercial subscription for project '%(product_name)s' in
+Launchpad has expired.
+%(commercial_use_expiration)s
+
+'%(product_name)s' was deactivated because its license is
+Other/Proprietary and requires a commercial subscription to use
+Launchpad's services. If you wish to reactivate the project contact us
+at commercial@xxxxxxxxxxxxx to discuss your options.
+
+A commercial subscription allows you to host your commercial project
+on Launchpad in the same way as any other project. Project's with a
+commercial subscription may have private-by-default bugs, and you
+may also request the setup of private code hosting.
+
+As the maintainer of a project with a commercial subscription, you
+may create private teams with private mailing lists and private package
+archives. Find out more about this here:
+https://help.launchpad.net/CommercialHosting
+
+Launchpad is a collaboration site, free to use for projects with an
+approved open source license. When you registered your project, you'd
+have seen a list of licenses presented. These are the licences we
+automatically recognise.
+
+If you have a different licence to one on the approved list, it must
+follow the guidelines we list on the following page in order to be
+approved: https://help.launchpad.net/Legal/ProjectLicensing
+
+Want to know more?
+Further information is on our FAQ "Can closed-source or proprietary
+projects use Launchpad?" The link is here:
+https://answers.launchpad.net/launchpad/+faq/208
+
+If the license for your project needs to be corrected, you can do this
+by following the 'Change Details' link on your project's overview page.
+
+Thanks,
+
+The Launchpad team.
=== modified file 'lib/lp/registry/interfaces/productjob.py'
--- lib/lp/registry/interfaces/productjob.py 2012-03-24 12:41:36 +0000
+++ lib/lp/registry/interfaces/productjob.py 2012-04-19 18:03:32 +0000
@@ -9,6 +9,12 @@
'IProductJobSource',
'IProductNotificationJob',
'IProductNotificationJobSource',
+ 'ICommercialExpiredJob',
+ 'ICommercialExpiredJobSource',
+ 'ISevenDayCommercialExpirationJob',
+ 'ISevenDayCommercialExpirationJobSource',
+ 'IThirtyDayCommercialExpirationJob',
+ 'IThirtyDayCommercialExpirationJobSource',
]
from zope.interface import Attribute
@@ -119,3 +125,53 @@
:param reply_to_commercial: Set the reply_to property to the
commercial email address.
"""
+
+
+class ISevenDayCommercialExpirationJob(IProductNotificationJob):
+ """A job that sends an email about an expiring commercial subscription."""
+
+
+class ISevenDayCommercialExpirationJobSource(IProductNotificationJobSource):
+ """An interface for creating `ISevenDayCommercialExpirationJob`s."""
+
+ def create(product, reviewer):
+ """Create a new `ISevenDayCommercialExpirationJob`.
+
+ :param product: An IProduct.
+ :param reviewer: The user or agent sending the email.
+ """
+
+
+class IThirtyDayCommercialExpirationJob(IProductNotificationJob):
+ """A job that sends an email about an expiring commercial subscription."""
+
+
+class IThirtyDayCommercialExpirationJobSource(IProductNotificationJobSource):
+ """An interface for creating `IThirtyDayCommercialExpirationJob`s."""
+
+ def create(product, reviewer):
+ """Create a new `IThirtyDayCommercialExpirationJob`.
+
+ :param product: An IProduct.
+ :param reviewer: The user or agent sending the email.
+ """
+
+
+class ICommercialExpiredJob(IProductNotificationJob):
+ """A job that sends an email about an expired commercial subscription.
+
+ This job is responsible for deactivating the project if it has a
+ proprietary license or deactivating the commercial features if the
+ license is open.
+ """
+
+
+class ICommercialExpiredJobSource(IProductNotificationJobSource):
+ """An interface for creating `IThirtyDayCommercialExpirationJob`s."""
+
+ def create(product, reviewer):
+ """Create a new `ICommercialExpiredJob`.
+
+ :param product: An IProduct.
+ :param reviewer: The user or agent sending the email.
+ """
=== modified file 'lib/lp/registry/model/productjob.py'
--- lib/lp/registry/model/productjob.py 2012-03-24 12:36:13 +0000
+++ lib/lp/registry/model/productjob.py 2012-04-19 18:03:32 +0000
@@ -6,6 +6,9 @@
__metaclass__ = type
__all__ = [
'ProductJob',
+ 'CommercialExpiredJob',
+ 'SevenDayCommercialExpirationJob',
+ 'ThirtyDayCommercialExpirationJob',
]
from lazr.delegates import delegates
@@ -21,15 +24,25 @@
classProvides,
implements,
)
+from zope.security.proxy import removeSecurityProxy
from lp.registry.enums import ProductJobType
from lp.registry.interfaces.person import IPersonSet
-from lp.registry.interfaces.product import IProduct
+from lp.registry.interfaces.product import (
+ IProduct,
+ License,
+ )
from lp.registry.interfaces.productjob import (
IProductJob,
IProductJobSource,
IProductNotificationJob,
IProductNotificationJobSource,
+ ICommercialExpiredJob,
+ ICommercialExpiredJobSource,
+ ISevenDayCommercialExpirationJob,
+ ISevenDayCommercialExpirationJobSource,
+ IThirtyDayCommercialExpirationJob,
+ IThirtyDayCommercialExpirationJobSource,
)
from lp.registry.model.product import Product
from lp.services.config import config
@@ -282,3 +295,94 @@
'Launchpad', config.canonical.noreply_from_address)
self.sendEmailToMaintainer(
self.email_template_name, self.subject, from_address)
+
+
+class CommericialExpirationMixin:
+
+ _email_template_name = 'product-commercial-subscription-expiration'
+ _subject_template = (
+ 'The commercial subscription for %s in Launchpad is expiring')
+
+ @classmethod
+ def create(cls, product, reviewer):
+ """Create a job."""
+ subject = cls._subject_template % product.name
+ return super(CommericialExpirationMixin, cls).create(
+ product, cls._email_template_name, subject, reviewer,
+ reply_to_commercial=True)
+
+ @cachedproperty
+ def message_data(self):
+ """See `IProductNotificationJob`."""
+ data = super(CommericialExpirationMixin, self).message_data
+ commercial_subscription = self.product.commercial_subscription
+ iso_date = commercial_subscription.date_expires.date().isoformat()
+ extra_data = {
+ 'commercial_use_expiration': iso_date,
+ }
+ data.update(extra_data)
+ return data
+
+
+class SevenDayCommercialExpirationJob(CommericialExpirationMixin,
+ ProductNotificationJob):
+ """A job that sends an email about an expiring commercial subscription."""
+
+ implements(ISevenDayCommercialExpirationJob)
+ classProvides(ISevenDayCommercialExpirationJobSource)
+ class_job_type = ProductJobType.COMMERCIAL_EXPIRATION_7_DAYS
+
+
+class ThirtyDayCommercialExpirationJob(CommericialExpirationMixin,
+ ProductNotificationJob):
+ """A job that sends an email about an expiring commercial subscription."""
+
+ implements(IThirtyDayCommercialExpirationJob)
+ classProvides(IThirtyDayCommercialExpirationJobSource)
+ class_job_type = ProductJobType.COMMERCIAL_EXPIRATION_30_DAYS
+
+
+class CommercialExpiredJob(CommericialExpirationMixin, ProductNotificationJob):
+ """A job that sends an email about an expired commercial subscription."""
+
+ implements(ICommercialExpiredJob)
+ classProvides(ICommercialExpiredJobSource)
+ class_job_type = ProductJobType.COMMERCIAL_EXPIRED
+
+ _email_template_name = '' # email_template_name does not need this.
+ _subject_template = (
+ 'The commercial subscription for %s in Launchpad expired')
+
+ @property
+ def _is_proprietary(self):
+ """Does the product have a proprietary license?"""
+ return License.OTHER_PROPRIETARY in self.product.licenses
+
+ @property
+ def email_template_name(self):
+ """See `IProductNotificationJob`.
+
+ The email template is determined by the product's licenses.
+ """
+ if self._is_proprietary:
+ return 'product-commercial-subscription-expired-proprietary'
+ return 'product-commercial-subscription-expired-open-source'
+
+ def _deactivateCommercialFeatures(self):
+ """Deactivate the project or just the commercial features it uses."""
+ if self._is_proprietary:
+ self.product.active = False
+ else:
+ removeSecurityProxy(self.product).private_bugs = False
+ for series in self.product.series:
+ if series.branch.private:
+ removeSecurityProxy(series).branch = None
+
+ def run(self):
+ """See `ProductNotificationJob`."""
+ if self.product.has_current_commercial_subscription:
+ # The commercial subscription was renewed after this job was
+ # created. Nothing needs to be done.
+ return
+ super(CommercialExpiredJob, self).run()
+ self._deactivateCommercialFeatures()
=== modified file 'lib/lp/registry/tests/test_productjob.py'
--- lib/lp/registry/tests/test_productjob.py 2012-03-24 12:41:36 +0000
+++ lib/lp/registry/tests/test_productjob.py 2012-04-19 18:03:32 +0000
@@ -11,17 +11,28 @@
)
import pytz
+from zope.component import getUtility
from zope.interface import (
classProvides,
implements,
)
from zope.security.proxy import removeSecurityProxy
+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.registry.enums import ProductJobType
+from lp.registry.interfaces.product import (
+ License,
+ )
from lp.registry.interfaces.productjob import (
IProductJob,
IProductJobSource,
IProductNotificationJobSource,
+ ICommercialExpiredJob,
+ ICommercialExpiredJobSource,
+ ISevenDayCommercialExpirationJob,
+ ISevenDayCommercialExpirationJobSource,
+ IThirtyDayCommercialExpirationJob,
+ IThirtyDayCommercialExpirationJobSource,
)
from lp.registry.interfaces.person import TeamSubscriptionPolicy
from lp.registry.interfaces.teammembership import TeamMembershipStatus
@@ -29,6 +40,9 @@
ProductJob,
ProductJobDerived,
ProductNotificationJob,
+ CommercialExpiredJob,
+ SevenDayCommercialExpirationJob,
+ ThirtyDayCommercialExpirationJob,
)
from lp.testing import (
person_logged_in,
@@ -225,6 +239,9 @@
self.assertIs(
True,
IProductNotificationJobSource.providedBy(ProductNotificationJob))
+ self.assertEqual(
+ ProductJobType.REVIEWER_NOTIFICATION,
+ ProductNotificationJob.class_job_type)
job = ProductNotificationJob.create(
product, email_template_name, subject, reviewer,
reply_to_commercial=False)
@@ -369,3 +386,183 @@
self.assertEqual(subject, notifications[0]['Subject'])
self.assertIn(
'Launchpad <noreply@xxxxxxxxxxxxx>', notifications[0]['From'])
+
+
+class CommericialExpirationMixin:
+
+ layer = DatabaseFunctionalLayer
+
+ EXPIRE_SUBSCRIPTION = False
+
+ def make_notification_data(self, licenses=[License.MIT]):
+ product = self.factory.makeProduct(licenses=licenses)
+ if License.OTHER_PROPRIETARY not in product.licenses:
+ # The proprietary project was automatically given a CS.
+ self.factory.makeCommercialSubscription(product)
+ reviewer = getUtility(ILaunchpadCelebrities).janitor
+ return product, reviewer
+
+ def test_create(self):
+ # Create an instance of an commercial expiration job that stores
+ # the notification information.
+ product = self.factory.makeProduct()
+ reviewer = getUtility(ILaunchpadCelebrities).janitor
+ self.assertIs(
+ True,
+ self.JOB_SOURCE_INTERFACE.providedBy(self.JOB_CLASS))
+ self.assertEqual(
+ self.JOB_CLASS_TYPE, self.JOB_CLASS.class_job_type)
+ job = self.JOB_CLASS.create(product, reviewer)
+ self.assertIsInstance(job, self.JOB_CLASS)
+ self.assertIs(
+ True, self.JOB_INTERFACE.providedBy(job))
+ self.assertEqual(product, job.product)
+ self.assertEqual(job._subject_template % product.name, job.subject)
+ self.assertEqual(reviewer, job.reviewer)
+ self.assertEqual(True, job.reply_to_commercial)
+
+ def test_email_template_name(self):
+ # The class defines the email_template_name.
+ product, reviewer = self.make_notification_data()
+ job = self.JOB_CLASS.create(product, reviewer)
+ self.assertEqual(job.email_template_name, job._email_template_name)
+
+ def test_message_data(self):
+ # The commercial expiration data is added.
+ product, reviewer = self.make_notification_data()
+ job = self.JOB_CLASS.create(product, reviewer)
+ commercial_subscription = product.commercial_subscription
+ iso_date = commercial_subscription.date_expires.date().isoformat()
+ self.assertEqual(
+ iso_date, job.message_data['commercial_use_expiration'])
+
+ def test_run(self):
+ # Smoke test that run() can make the email from the template and data.
+ product, reviewer = self.make_notification_data(
+ licenses=[License.OTHER_PROPRIETARY])
+ commercial_subscription = product.commercial_subscription
+ if self.EXPIRE_SUBSCRIPTION:
+ expired_date = (
+ commercial_subscription.date_expires - timedelta(days=365))
+ removeSecurityProxy(
+ commercial_subscription).date_expires = expired_date
+ iso_date = commercial_subscription.date_expires.date().isoformat()
+ job = self.JOB_CLASS.create(product, reviewer)
+ pop_notifications()
+ job.run()
+ notifications = pop_notifications()
+ self.assertEqual(1, len(notifications))
+ self.assertIn(iso_date, notifications[0].get_payload())
+
+
+class SevenDayCommercialExpirationJobTestCase(CommericialExpirationMixin,
+ TestCaseWithFactory):
+ """Test case for the SevenDayCommercialExpirationJob class."""
+
+ JOB_INTERFACE = ISevenDayCommercialExpirationJob
+ JOB_SOURCE_INTERFACE = ISevenDayCommercialExpirationJobSource
+ JOB_CLASS = SevenDayCommercialExpirationJob
+ JOB_CLASS_TYPE = ProductJobType.COMMERCIAL_EXPIRATION_7_DAYS
+
+
+class ThirtyDayCommercialExpirationJobTestCase(CommericialExpirationMixin,
+ TestCaseWithFactory):
+ """Test case for the SevenDayCommercialExpirationJob class."""
+
+ JOB_INTERFACE = IThirtyDayCommercialExpirationJob
+ JOB_SOURCE_INTERFACE = IThirtyDayCommercialExpirationJobSource
+ JOB_CLASS = ThirtyDayCommercialExpirationJob
+ JOB_CLASS_TYPE = ProductJobType.COMMERCIAL_EXPIRATION_30_DAYS
+
+
+class CommercialExpiredJobTestCase(CommericialExpirationMixin,
+ TestCaseWithFactory):
+ """Test case for the CommercialExpiredJob class."""
+
+ EXPIRE_SUBSCRIPTION = True
+ JOB_INTERFACE = ICommercialExpiredJob
+ JOB_SOURCE_INTERFACE = ICommercialExpiredJobSource
+ JOB_CLASS = CommercialExpiredJob
+ JOB_CLASS_TYPE = ProductJobType.COMMERCIAL_EXPIRED
+
+ def test_is_proprietary_open_source(self):
+ product, reviewer = self.make_notification_data(licenses=[License.MIT])
+ job = CommercialExpiredJob.create(product, reviewer)
+ self.assertIs(False, job._is_proprietary)
+
+ def test_is_proprietary_proprietary(self):
+ product, reviewer = self.make_notification_data(
+ licenses=[License.OTHER_PROPRIETARY])
+ job = CommercialExpiredJob.create(product, reviewer)
+ self.assertIs(True, job._is_proprietary)
+
+ def test_email_template_name(self):
+ # Redefine the inherited test to verify the open source license case.
+ # The state of the product's license defines the email_template_name.
+ product, reviewer = self.make_notification_data(licenses=[License.MIT])
+ job = CommercialExpiredJob.create(product, reviewer)
+ self.assertEqual(
+ 'product-commercial-subscription-expired-open-source',
+ job.email_template_name)
+
+ def test_email_template_name_proprietary(self):
+ # The state of the product's license defines the email_template_name.
+ product, reviewer = self.make_notification_data(
+ licenses=[License.OTHER_PROPRIETARY])
+ job = CommercialExpiredJob.create(product, reviewer)
+ self.assertEqual(
+ 'product-commercial-subscription-expired-proprietary',
+ job.email_template_name)
+
+ def test_deactivateCommercialFeatures_proprietary(self):
+ # When the project is proprietary, the product is deactivated.
+ product, reviewer = self.make_notification_data(
+ licenses=[License.OTHER_PROPRIETARY])
+ job = CommercialExpiredJob.create(product, reviewer)
+ job._deactivateCommercialFeatures()
+ self.assertIs(False, product.active)
+
+ def test_deactivateCommercialFeatures_open_source(self):
+ # When the project is open source, the product's commercial features
+ # are deactivated.
+ product, reviewer = self.make_notification_data(licenses=[License.MIT])
+ public_branch = self.factory.makeBranch(
+ owner=product.owner, product=product)
+ private_branch = self.factory.makeBranch(
+ owner=product.owner, product=product, private=True)
+ with person_logged_in(product.owner):
+ product.setPrivateBugs(True, product.owner)
+ public_series = product.development_focus
+ public_series.branch = public_branch
+ private_series = product.newSeries(
+ product.owner, 'special', 'testing', branch=private_branch)
+ job = CommercialExpiredJob.create(product, reviewer)
+ job._deactivateCommercialFeatures()
+ self.assertIs(True, product.active)
+ self.assertIs(False, product.private_bugs)
+ self.assertEqual(public_branch, public_series.branch)
+ self.assertIs(None, private_series.branch)
+
+ def test_run_deactivation_performed(self):
+ # An email is sent and the deactivation steps are performed.
+ product, reviewer = self.make_notification_data(
+ licenses=[License.OTHER_PROPRIETARY])
+ expired_date = (
+ product.commercial_subscription.date_expires - timedelta(days=365))
+ removeSecurityProxy(
+ product.commercial_subscription).date_expires = expired_date
+ job = CommercialExpiredJob.create(product, reviewer)
+ job.run()
+ self.assertIs(False, product.active)
+
+ def test_run_deactivation_aborted(self):
+ # The deactivation steps and email are aborted if the commercial
+ # subscription was renewed after the job was created.
+ product, reviewer = self.make_notification_data(
+ licenses=[License.OTHER_PROPRIETARY])
+ job = CommercialExpiredJob.create(product, reviewer)
+ pop_notifications()
+ job.run()
+ notifications = pop_notifications()
+ self.assertEqual(0, len(notifications))
+ self.assertIs(True, product.active)
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2012-04-10 20:24:43 +0000
+++ lib/lp/testing/factory.py 2012-04-19 18:03:32 +0000
@@ -4418,6 +4418,9 @@
def makeCommercialSubscription(self, product, expired=False):
"""Create a commercial subscription for the given product."""
+ if CommercialSubscription.selectOneBy(product=product) is not None:
+ raise AssertionError(
+ "The product under test already has a CommercialSubscription.")
if expired:
expiry = datetime.now(pytz.UTC) - timedelta(days=1)
else: