launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02034
[Merge] lp:~salgado/launchpad/expose-blueprints into lp:launchpad
Guilherme Salgado has proposed merging lp:~salgado/launchpad/expose-blueprints into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#146389 api for blueprint tracker
https://bugs.launchpad.net/bugs/146389
This branch exposes ISpecification attributes on the 'devel' version of the webservice.
I left out some controversial fields (i.e. .productseries, .distroseries) and exported .target as read-only for simplicity and because this diff is already too big.
Most of the changes here come from https://code.launchpad.net/~james-w/launchpad/expose-blueprints/+merge/30026; I just had to solve conflicts, clean some things up and unexport the controversial fields. The diff is quite long but it's mostly mechanical changes and tests.
--
https://code.launchpad.net/~salgado/launchpad/expose-blueprints/+merge/41898
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~salgado/launchpad/expose-blueprints into lp:launchpad.
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-17 22:18:34 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-25 19:32:28 +0000
@@ -34,6 +34,10 @@
)
from lp.blueprints.interfaces.specification import ISpecification
from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
+from lp.blueprints.interfaces.specificationtarget import (
+ IHasSpecifications,
+ ISpecificationTarget,
+ )
from lp.bugs.interfaces.bug import (
IBug,
IFrontPageBugAddForm,
@@ -516,3 +520,13 @@
# IProductSeries
patch_reference_property(IProductSeries, 'product', IProduct)
+
+# ISpecificationTarget
+patch_entry_return_type(
+ ISpecificationTarget, 'getSpecification', ISpecification)
+
+# IHasSpecifications
+patch_collection_property(
+ IHasSpecifications, 'all_specifications', ISpecification)
+patch_collection_property(
+ IHasSpecifications, 'valid_specifications', ISpecification)
=== modified file 'lib/lp/app/doc/tales.txt'
--- lib/lp/app/doc/tales.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/app/doc/tales.txt 2010-11-25 19:32:28 +0000
@@ -673,8 +673,11 @@
Blueprints
..........
+ >>> from lp.blueprints.interfaces.specification import (
+ ... SpecificationPriority)
>>> login('test@xxxxxxxxxxxxx')
- >>> specification = factory.makeSpecification()
+ >>> specification = factory.makeSpecification(
+ ... priority=SpecificationPriority.UNDEFINED)
>>> test_tales("specification/fmt:link", specification=specification)
u'<a...class="sprite blueprint-undefined">...</a>'
@@ -682,7 +685,8 @@
Blueprint branches
..................
- >>> specification = factory.makeSpecification()
+ >>> specification = factory.makeSpecification(
+ ... priority=SpecificationPriority.UNDEFINED)
>>> branch = factory.makeAnyBranch()
>>> specification_branch = specification.linkBranch(branch, branch.owner)
>>> test_tales("specification_branch/fmt:link",
=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py 2010-11-23 20:19:24 +0000
+++ lib/lp/blueprints/interfaces/specification.py 2010-11-25 19:32:28 +0000
@@ -19,7 +19,12 @@
]
-from lazr.restful.declarations import export_as_webservice_entry
+from lazr.restful.declarations import (
+ exported,
+ export_as_webservice_entry,
+ )
+from lazr.restful.fields import ReferenceChoice
+
from zope.component import getUtility
from zope.interface import (
Attribute,
@@ -44,9 +49,13 @@
SpecificationLifecycleStatus,
SpecificationPriority,
)
-from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
+from lp.blueprints.interfaces.specificationtarget import (
+ IHasSpecifications,
+ ISpecificationTarget,
+ )
from lp.blueprints.interfaces.sprint import ISprint
from lp.code.interfaces.branchlink import IHasLinkedBranches
+from lp.registry.interfaces.milestone import IMilestone
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.registry.interfaces.role import IHasOwner
from lp.services.fields import (
@@ -119,48 +128,66 @@
class INewSpecification(Interface):
"""A schema for a new specification."""
- name = SpecNameField(
- title=_('Name'), required=True, readonly=False,
- description=_(
- "May contain lower-case letters, numbers, and dashes. "
- "It will be used in the specification url. "
- "Examples: mozilla-type-ahead-find, postgres-smart-serial."))
- title = Title(
- title=_('Title'), required=True, description=_(
- "Describe the feature as clearly as possible in up to 70 "
- "characters. This title is displayed in every feature "
- "list or report."))
- specurl = SpecURLField(
- title=_('Specification URL'), required=False,
- description=_(
- "The URL of the specification. This is usually a wiki page."),
- constraint=valid_webref)
- summary = Summary(
- title=_('Summary'), required=True, description=_(
- "A single-paragraph description of the feature. "
- "This will also be displayed in most feature listings."))
- definition_status = Choice(
- title=_('Definition Status'),
- vocabulary=SpecificationDefinitionStatus,
- default=SpecificationDefinitionStatus.NEW,
- description=_(
- "The current status of the process to define the "
- "feature and get approval for the implementation plan."))
- assignee = PublicPersonChoice(
- title=_('Assignee'), required=False,
- description=_("The person responsible for implementing the feature."),
- vocabulary='ValidPersonOrTeam')
- drafter = PublicPersonChoice(
- title=_('Drafter'), required=False,
- description=_(
- "The person responsible for drafting the specification."),
- vocabulary='ValidPersonOrTeam')
- approver = PublicPersonChoice(
- title=_('Approver'), required=False,
- description=_(
- "The person responsible for approving the specification, "
- "and for reviewing the code when it's ready to be landed."),
- vocabulary='ValidPersonOrTeam')
+ name = exported(
+ SpecNameField(
+ title=_('Name'), required=True, readonly=False,
+ description=_(
+ "May contain lower-case letters, numbers, and dashes. "
+ "It will be used in the specification url. "
+ "Examples: mozilla-type-ahead-find, postgres-smart-serial.")),
+ ('devel', dict(exported=True)), exported=False)
+ title = exported(
+ Title(
+ title=_('Title'), required=True, description=_(
+ "Describe the feature as clearly as possible in up to 70 "
+ "characters. This title is displayed in every feature "
+ "list or report.")),
+ ('devel', dict(exported=True)), exported=False)
+ specurl = exported(
+ SpecURLField(
+ title=_('Specification URL'), required=False,
+ description=_(
+ "The URL of the specification. This is usually a wiki page."),
+ constraint=valid_webref),
+ ('devel', dict(exported=True, exported_as='specification_url')),
+ exported=False)
+ summary = exported(
+ Summary(
+ title=_('Summary'), required=True, description=_(
+ "A single-paragraph description of the feature. "
+ "This will also be displayed in most feature listings.")),
+ ('devel', dict(exported=True)), exported=False)
+ definition_status = exported(
+ Choice(
+ title=_('Definition Status'),
+ vocabulary=SpecificationDefinitionStatus,
+ default=SpecificationDefinitionStatus.NEW,
+ description=_(
+ "The current status of the process to define the "
+ "feature and get approval for the implementation plan.")),
+ ('devel', dict(exported=True)), exported=False)
+ assignee = exported(
+ PublicPersonChoice(
+ title=_('Assignee'), required=False,
+ description=_(
+ "The person responsible for implementing the feature."),
+ vocabulary='ValidPersonOrTeam'),
+ ('devel', dict(exported=True)), exported=False)
+ drafter = exported(
+ PublicPersonChoice(
+ title=_('Drafter'), required=False,
+ description=_(
+ "The person responsible for drafting the specification."),
+ vocabulary='ValidPersonOrTeam'),
+ ('devel', dict(exported=True)), exported=False)
+ approver = exported(
+ PublicPersonChoice(
+ title=_('Approver'), required=False,
+ description=_(
+ "The person responsible for approving the specification, "
+ "and for reviewing the code when it's ready to be landed."),
+ vocabulary='ValidPersonOrTeam'),
+ ('devel', dict(exported=True)), exported=False)
class INewSpecificationProjectTarget(Interface):
@@ -201,10 +228,15 @@
Requires the user to specify a distribution or a product as a target.
"""
- target = Choice(title=_("For"),
- description=_("The project for which this proposal is "
- "being made."),
- required=True, vocabulary='DistributionOrProduct')
+ # Exported as readonly for simplicity, but could be exported as read-write
+ # using setTarget() as the mutator.
+ target = exported(
+ ReferenceChoice(
+ title=_('For'), required=True, vocabulary='DistributionOrProduct',
+ description=_(
+ "The project for which this proposal is being made."),
+ schema=ISpecificationTarget),
+ ('devel', dict(exported=True, readonly=True)), exported=False)
class ISpecificationEditRestricted(Interface):
@@ -235,40 +267,54 @@
# referencing it.
id = Int(title=_("Database ID"), required=True, readonly=True)
- priority = Choice(
- title=_('Priority'), vocabulary=SpecificationPriority,
- default=SpecificationPriority.UNDEFINED, required=True)
- datecreated = Datetime(
- title=_('Date Created'), required=True, readonly=True)
- owner = PublicPersonChoice(
- title=_('Owner'), required=True, readonly=True,
- vocabulary='ValidPersonOrTeam')
- # target
+ priority = exported(
+ Choice(
+ title=_('Priority'), vocabulary=SpecificationPriority,
+ default=SpecificationPriority.UNDEFINED, required=True),
+ ('devel', dict(exported=True)), exported=False)
+ datecreated = exported(
+ Datetime(
+ title=_('Date Created'), required=True, readonly=True),
+ ('devel', dict(exported=True, exported_as='date_created')),
+ exported=False)
+ owner = exported(
+ PublicPersonChoice(
+ title=_('Owner'), required=True, readonly=True,
+ vocabulary='ValidPersonOrTeam'),
+ ('devel', dict(exported=True)), exported=False)
+
product = Choice(title=_('Project'), required=False,
- vocabulary='Product')
+ vocabulary='Product')
distribution = Choice(title=_('Distribution'), required=False,
- vocabulary='Distribution')
+ vocabulary='Distribution')
- # series
- productseries = Choice(title=_('Series Goal'), required=False,
+ productseries = Choice(
+ title=_('Series Goal'), required=False,
vocabulary='FilteredProductSeries',
description=_(
- "Choose a series in which you would like to deliver "
- "this feature. Selecting '(no value)' will clear the goal."))
- distroseries = Choice(title=_('Series Goal'), required=False,
+ "Choose a series in which you would like to deliver "
+ "this feature. Selecting '(no value)' will clear the goal."))
+ distroseries = Choice(
+ title=_('Series Goal'), required=False,
vocabulary='FilteredDistroSeries',
description=_(
"Choose a series in which you would like to deliver "
"this feature. Selecting '(no value)' will clear the goal."))
# milestone
- milestone = Choice(
- title=_('Milestone'), required=False, vocabulary='Milestone',
- description=_(
- "The milestone in which we would like this feature to be "
- "delivered."))
+ milestone = exported(
+ ReferenceChoice(
+ title=_('Milestone'), required=False, vocabulary='Milestone',
+ description=_(
+ "The milestone in which we would like this feature to be "
+ "delivered."),
+ schema=IMilestone),
+ ('devel', dict(exported=True)), exported=False)
# nomination to a series for release management
+ # XXX: It'd be nice to export goal as read-only, but it's tricky because
+ # users will need to be aware of goalstatus as what's returned by .goal
+ # may not be the accepted goal.
goal = Attribute("The series for which this feature is a goal.")
goalstatus = Choice(
title=_('Goal Acceptance'), vocabulary=SpecificationGoalStatus,
@@ -283,10 +329,12 @@
date_goal_decided = Attribute("The date the spec was approved "
"or declined as a goal.")
- whiteboard = Text(title=_('Status Whiteboard'), required=False,
- description=_(
- "Any notes on the status of this spec you would like to make. "
- "Your changes will override the current text."))
+ whiteboard = exported(
+ Text(title=_('Status Whiteboard'), required=False,
+ description=_(
+ "Any notes on the status of this spec you would like to "
+ "make. Your changes will override the current text.")),
+ ('devel', dict(exported=True)), exported=False)
direction_approved = Bool(title=_('Basic direction approved?'),
required=False, default=False, description=_("Check this to "
"indicate that the drafter and assignee have satisfied the "
=== modified file 'lib/lp/blueprints/interfaces/specificationtarget.py'
--- lib/lp/blueprints/interfaces/specificationtarget.py 2010-08-20 20:31:18 +0000
+++ lib/lp/blueprints/interfaces/specificationtarget.py 2010-11-25 19:32:28 +0000
@@ -17,6 +17,22 @@
Attribute,
Interface,
)
+from zope.schema import TextLine
+
+from lazr.restful.declarations import (
+ exported,
+ export_as_webservice_entry,
+ export_read_operation,
+ operation_for_version,
+ operation_parameters,
+ operation_returns_entry,
+ )
+from lazr.restful.fields import (
+ CollectionField,
+ Reference,
+ )
+
+from canonical.launchpad import _
class IHasSpecifications(Interface):
@@ -26,16 +42,30 @@
associated with them, and you can use this interface to query those.
"""
- all_specifications = Attribute(
- 'A list of all specifications, regardless of status or approval '
- 'or completion, for this object.')
+ all_specifications = exported(
+ CollectionField(
+ title=_("All specifications"),
+ value_type=Reference(schema=Interface), # ISpecification, really.
+ readonly=True,
+ description=_(
+ 'A list of all specifications, regardless of status or '
+ 'approval or completion, for this object.')),
+ ('devel', dict(exported=True)), exported=False)
has_any_specifications = Attribute(
'A true or false indicator of whether or not this object has any '
'specifications associated with it, regardless of their status.')
- valid_specifications = Attribute(
- 'A list of all specifications that are not obsolete.')
+ valid_specifications = exported(
+ CollectionField(
+ title=_("Valid specifications"),
+ value_type=Reference(schema=Interface), # ISpecification, really.
+ readonly=True,
+ description=_(
+ 'All specifications that are not obsolete. When called from '
+ 'an ISpecificationGoal it will also exclude the ones that '
+ 'have not been accepted for that goal')),
+ ('devel', dict(exported=True)), exported=False)
latest_specifications = Attribute(
"The latest 5 specifications registered for this context.")
@@ -63,12 +93,18 @@
"""
-
class ISpecificationTarget(IHasSpecifications):
"""An interface for the objects which actually have unique
specifications directly attached to them.
"""
+ export_as_webservice_entry()
+
+ @operation_parameters(
+ name=TextLine(title=_('The name of the specification')))
+ @operation_returns_entry(Interface) # really ISpecification
+ @export_read_operation()
+ @operation_for_version('devel')
def getSpecification(name):
"""Returns the specification with the given name, for this target,
or None.
=== modified file 'lib/lp/blueprints/interfaces/webservice.py'
--- lib/lp/blueprints/interfaces/webservice.py 2010-11-09 16:25:22 +0000
+++ lib/lp/blueprints/interfaces/webservice.py 2010-11-25 19:32:28 +0000
@@ -16,6 +16,7 @@
from lp.blueprints.interfaces.specification import ISpecification
from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
+from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
# XXX: JonathanLange 2010-11-09 bug=673083: Legacy work-around for circular
# import bugs. Break this up into a per-package thing.
from canonical.launchpad.interfaces import _schema_circular_imports
=== modified file 'lib/lp/blueprints/stories/standalone/sprint-links.txt'
--- lib/lp/blueprints/stories/standalone/sprint-links.txt 2009-09-22 10:48:09 +0000
+++ lib/lp/blueprints/stories/standalone/sprint-links.txt 2010-11-25 19:32:28 +0000
@@ -13,10 +13,6 @@
>>> browser.open('http://blueprints.launchpad.dev/firefox/+spec/canvas')
>>> browser.isHtml
True
- >>> 'Accepted' in browser.contents # make sure the page is not polluted
- False
- >>> 'Proposed' in browser.contents # make sure the page is not polluted
- False
Then we are going to propose it for the meeting agenda:
=== added file 'lib/lp/blueprints/tests/test_implements.py'
--- lib/lp/blueprints/tests/test_implements.py 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/tests/test_implements.py 2010-11-25 19:32:28 +0000
@@ -0,0 +1,61 @@
+# Copyright 2009 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests that various objects implement specification-related interfaces."""
+
+__metaclass__ = type
+
+from canonical.testing import DatabaseFunctionalLayer
+from lp.blueprints.interfaces.specificationtarget import (
+ IHasSpecifications, ISpecificationTarget)
+from lp.testing import TestCaseWithFactory
+
+
+class ImplementsIHasSpecificationsTests(TestCaseWithFactory):
+ """Test that various objects implement IHasSpecifications."""
+ layer = DatabaseFunctionalLayer
+
+ def test_product_implements_IHasSpecifications(self):
+ product = self.factory.makeProduct()
+ self.assertProvides(product, IHasSpecifications)
+
+ def test_distribution_implements_IHasSpecifications(self):
+ product = self.factory.makeProduct()
+ self.assertProvides(product, IHasSpecifications)
+
+ def test_projectgroup_implements_IHasSpecifications(self):
+ projectgroup = self.factory.makeProject()
+ self.assertProvides(projectgroup, IHasSpecifications)
+
+ def test_person_implements_IHasSpecifications(self):
+ person = self.factory.makePerson()
+ self.assertProvides(person, IHasSpecifications)
+
+ def test_productseries_implements_IHasSpecifications(self):
+ productseries = self.factory.makeProductSeries()
+ self.assertProvides(productseries, IHasSpecifications)
+
+ def test_distroseries_implements_IHasSpecifications(self):
+ distroseries = self.factory.makeDistroSeries()
+ self.assertProvides(distroseries, IHasSpecifications)
+
+
+class ImplementsISpecificationTargetTests(TestCaseWithFactory):
+ """Test that various objects implement ISpecificationTarget."""
+ layer = DatabaseFunctionalLayer
+
+ def test_product_implements_ISpecificationTarget(self):
+ product = self.factory.makeProduct()
+ self.assertProvides(product, ISpecificationTarget)
+
+ def test_distribution_implements_ISpecificationTarget(self):
+ product = self.factory.makeProduct()
+ self.assertProvides(product, ISpecificationTarget)
+
+ def test_productseries_implements_ISpecificationTarget(self):
+ productseries = self.factory.makeProductSeries()
+ self.assertProvides(productseries, ISpecificationTarget)
+
+ def test_distroseries_implements_ISpecificationTarget(self):
+ distroseries = self.factory.makeDistroSeries()
+ self.assertProvides(distroseries, ISpecificationTarget)
=== added file 'lib/lp/blueprints/tests/test_webservice.py'
--- lib/lp/blueprints/tests/test_webservice.py 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/tests/test_webservice.py 2010-11-25 19:32:28 +0000
@@ -0,0 +1,410 @@
+# Copyright 2009 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Webservice unit tests related to Launchpad blueprints."""
+
+__metaclass__ = type
+
+from canonical.testing import DatabaseFunctionalLayer
+from canonical.launchpad.testing.pages import webservice_for_person
+from lp.blueprints.interfaces.specification import (
+ SpecificationDefinitionStatus,
+ SpecificationPriority)
+from lp.testing import (
+ launchpadlib_for, TestCaseWithFactory)
+
+
+class SpecificationWebserviceTestCase(TestCaseWithFactory):
+
+ def makeProduct(self):
+ return self.factory.makeProduct(name="fooix")
+
+ def makeDistribution(self):
+ return self.factory.makeDistribution(name="foobuntu")
+
+ def getLaunchpadlib(self):
+ user = self.factory.makePerson()
+ return launchpadlib_for("testing", user, version='devel')
+
+ def getSpecOnWebservice(self, spec_object):
+ launchpadlib = self.getLaunchpadlib()
+ if spec_object.product is not None:
+ pillar_name = spec_object.product.name
+ else:
+ pillar_name = spec_object.distribution.name
+ return launchpadlib.load(
+ str(launchpadlib._root_uri) + '/%s/+spec/%s'
+ % (pillar_name, spec_object.name))
+
+ def getPillarOnWebservice(self, pillar_obj):
+ launchpadlib = self.getLaunchpadlib()
+ return launchpadlib.load(
+ str(launchpadlib._root_uri) + '/' + pillar_obj.name)
+
+
+class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):
+ """Test accessing specification attributes over the webservice."""
+ layer = DatabaseFunctionalLayer
+
+ def makeSimpleSpecification(self):
+ self.name = "some-spec"
+ self.title = "some-title"
+ self.url = "http://example.org/some_url"
+ self.summary = "Some summary."
+ status = SpecificationDefinitionStatus.PENDINGAPPROVAL
+ self.definition_status = status.title
+ self.assignee_name = "james-w"
+ assignee = self.factory.makePerson(name=self.assignee_name)
+ self.drafter_name = "jml"
+ drafter = self.factory.makePerson(name=self.drafter_name)
+ self.approver_name = "bob"
+ approver = self.factory.makePerson(name=self.approver_name)
+ self.owner_name = "mary"
+ owner = self.factory.makePerson(name=self.owner_name)
+ priority = SpecificationPriority.HIGH
+ self.priority = priority.title
+ self.whiteboard = "Some whiteboard"
+ self.product = self.factory.makeProduct()
+ return self.factory.makeSpecification(
+ product=self.product, name=self.name,
+ title=self.title, specurl=self.url,
+ summary=self.summary,
+ status=status,
+ assignee=assignee, drafter=drafter, approver=approver,
+ priority=priority,
+ owner=owner, whiteboard=self.whiteboard)
+
+ def getSimpleSpecificationResponse(self):
+ self.spec_object = self.makeSimpleSpecification()
+ return self.getSpecOnWebservice(self.spec_object)
+
+ def test_representation_is_empty_on_1_dot_0(self):
+ # ISpecification is exposed on the 1.0 version so that they can be
+ # linked against branches, but none of its fields is exposed on that
+ # version as we expect it to undergo significant refactorings before
+ # it's ready for prime time.
+ spec = self.makeSimpleSpecification()
+ user = self.factory.makePerson()
+ webservice = webservice_for_person(user)
+ response = webservice.get(
+ '/%s/+spec/%s' % (spec.product.name, spec.name))
+ expected_keys = sorted(
+ [u'self_link', u'http_etag', u'resource_type_link'])
+ self.assertEqual(response.status, 200)
+ self.assertEqual(sorted(response.jsonBody().keys()), expected_keys)
+
+ def test_representation_contains_name(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.name, spec.name)
+
+ def test_representation_contains_target(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.product.name, spec.target.name)
+
+ def test_representation_contains_title(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.title, spec.title)
+
+ def test_representation_contains_specification_url(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.url, spec.specification_url)
+
+ def test_representation_contains_summary(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.summary, spec.summary)
+
+ def test_representation_contains_definition_status(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(
+ self.definition_status, spec.definition_status)
+
+ def test_representation_contains_assignee(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.assignee_name, spec.assignee.name)
+
+ def test_representation_contains_drafter(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.drafter_name, spec.drafter.name)
+
+ def test_representation_contains_approver(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.approver_name, spec.approver.name)
+
+ def test_representation_contains_owner(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.owner_name, spec.owner.name)
+
+ def test_representation_contains_priority(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.priority, spec.priority)
+
+ def test_representation_contains_date_created(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.spec_object.datecreated, spec.date_created)
+
+ def test_representation_contains_whiteboard(self):
+ spec = self.getSimpleSpecificationResponse()
+ self.assertEqual(self.whiteboard, spec.whiteboard)
+
+ def test_representation_contains_milestone(self):
+ product = self.makeProduct()
+ productseries = self.factory.makeProductSeries(product=product)
+ milestone = self.factory.makeMilestone(
+ name="1.0", product=product, productseries=productseries)
+ spec_object = self.factory.makeSpecification(
+ product=product, goal=productseries, milestone=milestone)
+ spec = self.getSpecOnWebservice(spec_object)
+ self.assertEqual("1.0", spec.milestone.name)
+
+
+class SpecificationTargetTests(SpecificationWebserviceTestCase):
+ """Tests for accessing specifications via their targets."""
+ layer = DatabaseFunctionalLayer
+
+ def test_get_specification_on_product(self):
+ product = self.makeProduct()
+ spec_object = self.factory.makeSpecification(
+ product=product, name="some-spec")
+ product_on_webservice = self.getPillarOnWebservice(product)
+ spec = product_on_webservice.getSpecification(name="some-spec")
+ self.assertEqual("some-spec", spec.name)
+ self.assertEqual("fooix", spec.target.name)
+
+ def test_get_specification_on_distribution(self):
+ distribution = self.makeDistribution()
+ spec_object = self.factory.makeSpecification(
+ distribution=distribution, name="some-spec")
+ distro_on_webservice = self.getPillarOnWebservice(distribution)
+ spec = distro_on_webservice.getSpecification(name="some-spec")
+ self.assertEqual("some-spec", spec.name)
+ self.assertEqual("foobuntu", spec.target.name)
+
+ def test_get_specification_on_productseries(self):
+ product = self.makeProduct()
+ productseries = self.factory.makeProductSeries(
+ product=product, name="fooix-dev")
+ spec_object = self.factory.makeSpecification(
+ product=product, name="some-spec", goal=productseries)
+ product_on_webservice = self.getPillarOnWebservice(product)
+ productseries_on_webservice = product_on_webservice.getSeries(
+ name="fooix-dev")
+ spec = productseries_on_webservice.getSpecification(name="some-spec")
+ self.assertEqual("some-spec", spec.name)
+ self.assertEqual("fooix", spec.target.name)
+
+ def test_get_specification_on_distroseries(self):
+ distribution = self.makeDistribution()
+ distroseries = self.factory.makeDistroSeries(
+ distribution=distribution, name="maudlin")
+ spec_object = self.factory.makeSpecification(
+ distribution=distribution, name="some-spec",
+ goal=distroseries)
+ distro_on_webservice = self.getPillarOnWebservice(distribution)
+ distroseries_on_webservice = distro_on_webservice.getSeries(
+ name_or_version="maudlin")
+ spec = distroseries_on_webservice.getSpecification(name="some-spec")
+ self.assertEqual("some-spec", spec.name)
+ self.assertEqual("foobuntu", spec.target.name)
+
+ def test_get_specification_not_found(self):
+ product = self.makeProduct()
+ product_on_webservice = self.getPillarOnWebservice(product)
+ spec = product_on_webservice.getSpecification(name="nonexistant")
+ self.assertEqual(None, spec)
+
+
+class IHasSpecificationsTests(SpecificationWebserviceTestCase):
+ """Tests for accessing IHasSpecifications methods over the webservice."""
+ layer = DatabaseFunctionalLayer
+
+ def assertNamesOfSpecificationsAre(self, expected_names, specifications):
+ names = [s.name for s in specifications]
+ self.assertEqual(sorted(expected_names), sorted(names))
+
+ def test_product_all_specifications(self):
+ product = self.makeProduct()
+ self.factory.makeSpecification(product=product, name="spec1")
+ self.factory.makeSpecification(product=product, name="spec2")
+ product_on_webservice = self.getPillarOnWebservice(product)
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"], product_on_webservice.all_specifications)
+
+ def test_product_valid_specifications(self):
+ product = self.makeProduct()
+ self.factory.makeSpecification(product=product, name="spec1")
+ self.factory.makeSpecification(
+ product=product, name="spec2",
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ product_on_webservice = self.getPillarOnWebservice(product)
+ self.assertNamesOfSpecificationsAre(
+ ["spec1"], product_on_webservice.valid_specifications)
+
+ def test_distribution_all_specifications(self):
+ distribution = self.makeDistribution()
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec1")
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec2")
+ distro_on_webservice = self.getPillarOnWebservice(distribution)
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"], distro_on_webservice.all_specifications)
+
+ def test_distribution_valid_specifications(self):
+ distribution = self.makeDistribution()
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec1")
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec2",
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ distro_on_webservice = self.getPillarOnWebservice(distribution)
+ self.assertNamesOfSpecificationsAre(
+ ["spec1"], distro_on_webservice.valid_specifications)
+
+ def test_distroseries_all_specifications(self):
+ distribution = self.makeDistribution()
+ distroseries = self.factory.makeDistroSeries(
+ name='maudlin', distribution=distribution)
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec1",
+ goal=distroseries)
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec2",
+ goal=distroseries)
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec3")
+ distro_on_webservice = self.getPillarOnWebservice(distribution)
+ distroseries_on_webservice = distro_on_webservice.getSeries(
+ name_or_version="maudlin")
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"],
+ distroseries_on_webservice.all_specifications)
+
+ # XXX: salgado, 2010-11-25, bug=681432: Test disabled because
+ # DistroSeries.valid_specifications is broken.
+ def disabled_test_distroseries_valid_specifications(self):
+ distribution = self.makeDistribution()
+ distroseries = self.factory.makeDistroSeries(
+ name='maudlin', distribution=distribution)
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec1",
+ goal=distroseries)
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec2",
+ goal=distroseries)
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec3",
+ goal=distroseries,
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ self.factory.makeSpecification(
+ distribution=distribution, name="spec4")
+ distro_on_webservice = self.getPillarOnWebservice(distribution)
+ distroseries_on_webservice = distro_on_webservice.getSeries(
+ name_or_version="maudlin")
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"],
+ distroseries_on_webservice.valid_specifications)
+
+ def test_productseries_all_specifications(self):
+ product = self.makeProduct()
+ productseries = self.factory.makeProductSeries(
+ product=product, name="fooix-dev")
+ self.factory.makeSpecification(
+ product=product, name="spec1", goal=productseries)
+ self.factory.makeSpecification(
+ product=product, name="spec2", goal=productseries)
+ self.factory.makeSpecification(product=product, name="spec3")
+ product_on_webservice = self.getPillarOnWebservice(product)
+ series_on_webservice = product_on_webservice.getSeries(
+ name="fooix-dev")
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"], series_on_webservice.all_specifications)
+
+ def test_productseries_valid_specifications(self):
+ product = self.makeProduct()
+ productseries = self.factory.makeProductSeries(
+ product=product, name="fooix-dev")
+ self.factory.makeSpecification(
+ product=product, name="spec1", goal=productseries)
+ self.factory.makeSpecification(
+ product=product, name="spec2", goal=productseries)
+ self.factory.makeSpecification(
+ product=product, name="spec3", goal=productseries,
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ self.factory.makeSpecification(product=product, name="spec4")
+ product_on_webservice = self.getPillarOnWebservice(product)
+ series_on_webservice = product_on_webservice.getSeries(
+ name="fooix-dev")
+ # Should this be different to the results for distroseries?
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"],
+ series_on_webservice.valid_specifications)
+
+ def test_projectgroup_all_specifications(self):
+ projectgroup = self.factory.makeProject()
+ other_projectgroup = self.factory.makeProject()
+ product1 = self.factory.makeProduct(project=projectgroup)
+ product2 = self.factory.makeProduct(project=projectgroup)
+ product3 = self.factory.makeProduct(project=other_projectgroup)
+ self.factory.makeSpecification(
+ product=product1, name="spec1")
+ self.factory.makeSpecification(
+ product=product2, name="spec2",
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ self.factory.makeSpecification(
+ product=product3, name="spec3")
+ projectgroup_on_webservice = self.getPillarOnWebservice(projectgroup)
+ # Should this be different to the results for distroseries?
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"],
+ projectgroup_on_webservice.all_specifications)
+
+ def test_projectgroup_valid_specifications(self):
+ projectgroup = self.factory.makeProject()
+ other_projectgroup = self.factory.makeProject()
+ product1 = self.factory.makeProduct(project=projectgroup)
+ product2 = self.factory.makeProduct(project=projectgroup)
+ product3 = self.factory.makeProduct(project=other_projectgroup)
+ self.factory.makeSpecification(
+ product=product1, name="spec1")
+ self.factory.makeSpecification(
+ product=product2, name="spec2",
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ self.factory.makeSpecification(
+ product=product3, name="spec3")
+ projectgroup_on_webservice = self.getPillarOnWebservice(projectgroup)
+ # Should this be different to the results for distroseries?
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"],
+ projectgroup_on_webservice.valid_specifications)
+
+ def test_person_all_specifications(self):
+ person = self.factory.makePerson(name="james-w")
+ product = self.factory.makeProduct()
+ self.factory.makeSpecification(
+ product=product, name="spec1", drafter=person)
+ self.factory.makeSpecification(
+ product=product, name="spec2", approver=person,
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ self.factory.makeSpecification(
+ product=product, name="spec3")
+ launchpadlib = self.getLaunchpadlib()
+ person_on_webservice = launchpadlib.load(
+ str(launchpadlib._root_uri) + '/~james-w')
+ self.assertNamesOfSpecificationsAre(
+ ["spec1", "spec2"], person_on_webservice.all_specifications)
+
+ def test_person_valid_specifications(self):
+ person = self.factory.makePerson(name="james-w")
+ product = self.factory.makeProduct()
+ self.factory.makeSpecification(
+ product=product, name="spec1", drafter=person)
+ self.factory.makeSpecification(
+ product=product, name="spec2", approver=person,
+ status=SpecificationDefinitionStatus.OBSOLETE)
+ self.factory.makeSpecification(
+ product=product, name="spec3")
+ launchpadlib = self.getLaunchpadlib()
+ person_on_webservice = launchpadlib.load(
+ str(launchpadlib._root_uri) + '/~james-w')
+ self.assertNamesOfSpecificationsAre(
+ ["spec1"], person_on_webservice.valid_specifications)
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-11-18 12:05:34 +0000
+++ lib/lp/testing/factory.py 2010-11-25 19:32:28 +0000
@@ -102,7 +102,11 @@
from lp.app.enums import ServiceUsage
from lp.archiveuploader.dscfile import DSCFile
from lp.archiveuploader.uploadpolicy import BuildDaemonUploadPolicy
-from lp.blueprints.enums import SpecificationDefinitionStatus
+from lp.blueprints.enums import (
+ SpecificationDefinitionStatus,
+ SpecificationGoalStatus,
+ SpecificationPriority,
+ )
from lp.blueprints.interfaces.specification import ISpecificationSet
from lp.blueprints.interfaces.sprint import ISprintSet
from lp.bugs.interfaces.bug import (
@@ -1671,7 +1675,9 @@
def makeSpecification(self, product=None, title=None, distribution=None,
name=None, summary=None, owner=None,
status=SpecificationDefinitionStatus.NEW,
- implementation_status=None):
+ implementation_status=None, goal=None, specurl=None,
+ assignee=None, drafter=None, approver=None,
+ priority=None, whiteboard=None, milestone=None):
"""Create and return a new, arbitrary Blueprint.
:param product: The product to make the blueprint on. If one is
@@ -1687,17 +1693,32 @@
title = self.getUniqueString('title')
if owner is None:
owner = self.makePerson()
+ if priority is None:
+ priority = SpecificationPriority.UNDEFINED
spec = getUtility(ISpecificationSet).new(
name=name,
title=title,
specurl=None,
summary=summary,
definition_status=status,
+ whiteboard=whiteboard,
owner=owner,
+ assignee=assignee,
+ drafter=drafter,
+ approver=approver,
product=product,
- distribution=distribution)
+ distribution=distribution,
+ priority=priority)
+ naked_spec = removeSecurityProxy(spec)
+ if status == SpecificationDefinitionStatus.OBSOLETE:
+ # This is to satisfy a DB constraint of obsolete specs.
+ naked_spec.completer = owner
+ naked_spec.date_completed = datetime.now(pytz.UTC)
+ naked_spec.specurl = specurl
+ naked_spec.milestone = milestone
+ if goal is not None:
+ naked_spec.proposeGoal(goal, spec.target.owner)
if implementation_status is not None:
- naked_spec = removeSecurityProxy(spec)
naked_spec.implementation_status = implementation_status
naked_spec.updateLifecycleStatus(owner)
return spec
Follow ups