← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~abentley/launchpad/new-tests into lp:launchpad

 

Aaron Bentley has proposed merging lp:~abentley/launchpad/new-tests into lp:launchpad.

Commit message:
Add tests for Sprint.specifications.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~abentley/launchpad/new-tests/+merge/126767

= Summary =
Add unit tests for Specification.sprint and sprint index.

== Pre-implementation notes ==
None

== LOC Rationale ==
Part of private projects

== Implementation details ==
Private blueprints require additional filtering which means updating Specification.sprint.  Since it does string concatentation, I wish to re-write it as a Storm expression.  Since it has no tests, we need to add them first.

This adds the tests, having first verified that all the tested parameters are used.  The following branch converts it to a Storm expression.

== Tests ==
bin/test test_sprint

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/blueprints/model/tests/test_sprint.py
  lib/lp/testing/_webservice.py
  lib/lp/blueprints/browser/tests/test_sprint.py
-- 
https://code.launchpad.net/~abentley/launchpad/new-tests/+merge/126767
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/new-tests into lp:launchpad.
=== modified file 'lib/lp/blueprints/browser/tests/test_sprint.py'
--- lib/lp/blueprints/browser/tests/test_sprint.py	2012-06-20 18:35:49 +0000
+++ lib/lp/blueprints/browser/tests/test_sprint.py	2012-09-27 19:20:28 +0000
@@ -1,4 +1,4 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for Sprint pages and views."""
@@ -6,13 +6,15 @@
 __metaclass__ = type
 
 from storm.locals import Store
+from testtools.matchers import Equals
 
-from lp.testing import TestCaseWithFactory
+from lp.testing import BrowserTestCase
 from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.matchers import BrowsesWithQueryLimit
-
-
-class TestSprintIndex(TestCaseWithFactory):
+from lp.testing.matchers import BrowsesWithQueryLimit, HasQueryCount
+from lp.testing._webservice import QueryCollector
+
+
+class TestSprintIndex(BrowserTestCase):
 
     layer = DatabaseFunctionalLayer
 
@@ -27,3 +29,14 @@
         Store.of(sprint).flush()
         Store.of(sprint).invalidate()
         self.assertThat(sprint, BrowsesWithQueryLimit(18, sprint.owner))
+
+    def test_blueprint_listing_query_count(self):
+        """Set a maximum number of queries for sprint blueprint lists."""
+        sprint = self.factory.makeSprint()
+        for count in range(10):
+            blueprint = self.factory.makeSpecification()
+            link = blueprint.linkSprint(sprint, blueprint.owner)
+            link.acceptBy(sprint.owner)
+        with QueryCollector() as recorder:
+            self.getViewBrowser(sprint)
+        self.assertThat(recorder, HasQueryCount(Equals(33)))

=== modified file 'lib/lp/blueprints/model/tests/test_sprint.py'
--- lib/lp/blueprints/model/tests/test_sprint.py	2012-01-01 02:58:52 +0000
+++ lib/lp/blueprints/model/tests/test_sprint.py	2012-09-27 19:20:28 +0000
@@ -5,10 +5,184 @@
 
 __metaclass__ = type
 
+
+import datetime
+
+from pytz import utc
+from zope.security.proxy import removeSecurityProxy
+
+from lp.blueprints.enums import (
+    NewSpecificationDefinitionStatus,
+    SpecificationDefinitionStatus,
+    SpecificationFilter,
+    SpecificationPriority,
+    SpecificationSort,
+    )
 from lp.testing import TestCaseWithFactory
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
+def list_result(sprint, filter=None):
+    result = sprint.specifications(SpecificationSort.DATE, filter=filter)
+    return list(result)
+
+
+class TestSpecifications(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestSpecifications, self).setUp()
+        self.date_decided = datetime.datetime.now(utc)
+
+    def makeSpec(self, sprint=None, date_decided=0, date_created=0,
+                 proposed=False, declined=False, title=None,
+                 status=NewSpecificationDefinitionStatus.NEW,
+                 name=None, priority=None):
+        if sprint is None:
+            sprint = self.factory.makeSprint()
+        blueprint = self.factory.makeSpecification(
+            title=title, status=status, name=name, priority=priority)
+        link = blueprint.linkSprint(sprint, blueprint.owner)
+        naked_link = removeSecurityProxy(link)
+        if declined:
+            link.declineBy(sprint.owner)
+        elif not proposed:
+            link.acceptBy(sprint.owner)
+        if not proposed:
+            date_decided = self.date_decided + datetime.timedelta(date_decided)
+            naked_link.date_decided = date_decided
+        date_created = self.date_decided + datetime.timedelta(date_created)
+        naked_link.date_created = date_created
+        return blueprint
+
+    def test_specifications_quantity(self):
+        # Ensure the quantity controls the maximum number of entries.
+        sprint = self.factory.makeSprint()
+        for count in range(10):
+            self.makeSpec(sprint)
+        self.assertEqual(10, sprint.specifications().count())
+        self.assertEqual(10, sprint.specifications(quantity=None).count())
+        self.assertEqual(8, sprint.specifications(quantity=8).count())
+        self.assertEqual(10, sprint.specifications(quantity=11).count())
+
+    def test_specifications_date_sort_accepted_decided(self):
+        # If only accepted proposals are requested, date-sorting uses
+        # date_decided.  Otherwise, it uses date_created.
+        sprint = self.factory.makeSprint()
+        blueprint1 = self.makeSpec(sprint, date_decided=0, date_created=0)
+        blueprint2 = self.makeSpec(sprint, date_decided=-1, date_created=1)
+        blueprint3 = self.makeSpec(sprint, date_decided=1, date_created=2)
+        result = list_result(sprint)
+        self.assertEqual([blueprint3, blueprint1, blueprint2], result)
+        # SpecificationFilter.ALL forces sorting by date_created, since not
+        # all entries will have date_decided.
+        result = list_result(sprint, [SpecificationFilter.ALL])
+        self.assertEqual([blueprint3, blueprint2, blueprint1], result)
+
+    def test_accepted_date_sort_creation(self):
+        # If date_decided does not vary, sort on date_created.
+        sprint = self.factory.makeSprint()
+        blueprint1 = self.makeSpec(sprint, date_created=0)
+        blueprint2 = self.makeSpec(sprint, date_created=-1)
+        blueprint3 = self.makeSpec(sprint, date_created=1)
+        result = list_result(sprint)
+        self.assertEqual([blueprint3, blueprint1, blueprint2], result)
+        result = list_result(sprint, [SpecificationFilter.ALL])
+        self.assertEqual([blueprint3, blueprint1, blueprint2], result)
+
+    def test_proposed_date_sort_creation(self):
+        # date-sorting by PROPOSED uses date_created.
+        sprint = self.factory.makeSprint()
+        blueprint1 = self.makeSpec(sprint, date_created=0, proposed=True)
+        blueprint2 = self.makeSpec(sprint, date_created=-1, proposed=True)
+        blueprint3 = self.makeSpec(sprint, date_created=1, proposed=True)
+        result = list_result(sprint, [SpecificationFilter.PROPOSED])
+        self.assertEqual([blueprint3, blueprint1, blueprint2], result)
+
+    def test_accepted_date_sort_id(self):
+        # date-sorting when no date varies uses object id.
+        sprint = self.factory.makeSprint()
+        blueprint1 = self.makeSpec(sprint)
+        blueprint2 = self.makeSpec(sprint)
+        blueprint3 = self.makeSpec(sprint)
+        result = list_result(sprint)
+        self.assertEqual([blueprint1, blueprint2, blueprint3], result)
+        # date-sorting ALL when no date varies uses object id.
+        result = list_result(sprint, [SpecificationFilter.ALL])
+        self.assertEqual([blueprint1, blueprint2, blueprint3], result)
+
+    def test_proposed_date_sort_id(self):
+        # date-sorting PROPOSED when no date varies uses object id.
+        sprint = self.factory.makeSprint()
+        blueprint1 = self.makeSpec(sprint, proposed=True)
+        blueprint2 = self.makeSpec(sprint, proposed=True)
+        blueprint3 = self.makeSpec(sprint, proposed=True)
+        result = list_result(sprint, [SpecificationFilter.PROPOSED])
+        self.assertEqual([blueprint1, blueprint2, blueprint3], result)
+
+    def test_priority_sort(self):
+        # Sorting by priority works and is the default.
+        # When priority is supplied, status is ignored.
+        blueprint1 = self.makeSpec(priority=SpecificationPriority.UNDEFINED,
+                                   status=SpecificationDefinitionStatus.NEW)
+        sprint = blueprint1.sprints[0]
+        blueprint2 = self.makeSpec(
+            sprint, priority=SpecificationPriority.NOTFORUS,
+            status=SpecificationDefinitionStatus.APPROVED)
+        blueprint3 = self.makeSpec(
+            sprint, priority=SpecificationPriority.LOW,
+            status=SpecificationDefinitionStatus.OBSOLETE)
+        result = sprint.specifications()
+        self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
+        result = sprint.specifications(sort=SpecificationSort.PRIORITY)
+        self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
+
+    def test_priority_sort_fallback_status(self):
+        # Sorting by priority falls back to defintion_status.
+        # When status is supplied, name is ignored.
+        blueprint1 = self.makeSpec(
+            status=SpecificationDefinitionStatus.OBSOLETE, name='a')
+        sprint = blueprint1.sprints[0]
+        blueprint2 = self.makeSpec(
+            sprint, status=SpecificationDefinitionStatus.APPROVED, name='c')
+        blueprint3 = self.makeSpec(
+            sprint, status=SpecificationDefinitionStatus.NEW, name='b')
+        result = sprint.specifications()
+        self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
+        result = sprint.specifications(sort=SpecificationSort.PRIORITY)
+        self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
+
+    def test_priority_sort_fallback_name(self):
+        # Sorting by priority falls back to name
+        blueprint1 = self.makeSpec(name='b')
+        sprint = blueprint1.sprints[0]
+        blueprint2 = self.makeSpec(sprint, name='c')
+        blueprint3 = self.makeSpec(sprint, name='a')
+        result = sprint.specifications()
+        self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
+        result = sprint.specifications(sort=SpecificationSort.PRIORITY)
+        self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
+
+    def test_text_search(self):
+        # Text searches work.
+        blueprint1 = self.makeSpec(title='abc')
+        sprint = blueprint1.sprints[0]
+        blueprint2 = self.makeSpec(sprint, title='def')
+        result = list_result(sprint, ['abc'])
+        self.assertEqual([blueprint1], result)
+        result = list_result(sprint, ['def'])
+        self.assertEqual([blueprint2], result)
+
+    def test_declined(self):
+        # Specifying SpecificationFilter.DECLINED shows only declined specs.
+        blueprint1 = self.makeSpec()
+        sprint = blueprint1.sprints[0]
+        blueprint2 = self.makeSpec(sprint, declined=True)
+        result = list_result(sprint, [SpecificationFilter.DECLINED])
+        self.assertEqual([blueprint2], result)
+
+
 class TestSprintAttendancesSort(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer
@@ -26,5 +200,6 @@
         sprint.attend(
             dave, sprint.time_starts, sprint.time_ends, True)
         attendances = [bob.displayname, ced.displayname, dave.displayname]
-        people = [attendee.attendee.displayname for attendee in sprint.attendances]
+        people = [attendee.attendee.displayname for attendee in
+                  sprint.attendances]
         self.assertEqual(attendances, people)

=== modified file 'lib/lp/testing/_webservice.py'
--- lib/lp/testing/_webservice.py	2012-01-01 02:58:52 +0000
+++ lib/lp/testing/_webservice.py	2012-09-27 19:20:28 +0000
@@ -190,6 +190,10 @@
         ztapi.subscribe((IEndRequestEvent, ), None, self)
         self._active = True
 
+    def __enter__(self):
+        self.register()
+        return self
+
     def __call__(self, event):
         if self._active:
             self.queries = get_request_statements()
@@ -197,3 +201,6 @@
 
     def unregister(self):
         self._active = False
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.unregister()


Follow ups