Merge lp:~abentley/launchpad/push-creates-package into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 13690
Proposed branch: lp:~abentley/launchpad/push-creates-package
Merge into: lp:launchpad
Diff against target: 543 lines (+162/-40)
15 files modified
lib/canonical/launchpad/xmlrpc/configure.zcml (+3/-0)
lib/canonical/launchpad/xmlrpc/faults.py (+11/-0)
lib/lp/code/tests/helpers.py (+14/-0)
lib/lp/code/xmlrpc/codehosting.py (+11/-0)
lib/lp/code/xmlrpc/tests/test_codehosting.py (+25/-5)
lib/lp/codehosting/inmemory.py (+21/-8)
lib/lp/codehosting/tests/test_acceptance.py (+19/-9)
lib/lp/codehosting/vfs/branchfs.py (+2/-5)
lib/lp/codehosting/vfs/tests/test_branchfs.py (+13/-2)
lib/lp/registry/errors.py (+5/-0)
lib/lp/registry/interfaces/person.py (+4/-8)
lib/lp/registry/model/person.py (+1/-1)
lib/lp/registry/model/sourcepackagename.py (+8/-1)
lib/lp/registry/tests/test_person.py (+1/-1)
lib/lp/registry/tests/test_sourcepackagename.py (+24/-0)
To merge this branch: bzr merge lp:~abentley/launchpad/push-creates-package
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+71366@code.launchpad.net

Commit message

Push creates source package names.

Description of the change

= Summary =
Fix bug #386596: pushing to a packaging branch can't create a new package

== Proposed fix ==
Support creating new source package names in createBranch

== Pre-implementation notes ==
None

== Implementation details ==
In the xmlrpc-backed version, catch NoSuchSourcePackageName and handle it by creating the name and doing a recursive call to createBranch.

In the memory-backed version, create the sourcepackagename if not already present.

In both cases, SourcePackageNameSet.new raises InvalidName if valid_name returns False. Accordingly, InvalidName moved from person.py to errors.py.

createBranch translates InvalidName to the new InvalidSourcePackageName fault.

The vfs transport converts InvalidSourcePackageName into PermissionDenied.

== Tests ==
bin/test -t test_createBranch_invalid_package_name -t test_createBranch_missing_sourcepackagename -t test_push_new_branch_of_non_existant_source_package_name -t t test_createBranch_invalid_package_name.

== Demo and Q/A ==

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/canonical/launchpad/xmlrpc/faults.py
  lib/lp/registry/errors.py
  lib/lp/code/xmlrpc/codehosting.py
  lib/lp/code/xmlrpc/tests/test_codehosting.py
  lib/lp/code/tests/helpers.py
  lib/canonical/launchpad/xmlrpc/configure.zcml
  lib/lp/codehosting/vfs/branchfs.py
  lib/lp/registry/model/sourcepackagename.py
  lib/lp/codehosting/inmemory.py
  lib/lp/registry/tests/test_sourcepackagename.py
  lib/lp/codehosting/vfs/tests/test_branchfs.py
  lib/lp/codehosting/tests/test_acceptance.py
  lib/lp/registry/interfaces/person.py
  lib/lp/registry/model/person.py
  lib/lp/registry/tests/test_person.py

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/xmlrpc/configure.zcml'
--- lib/canonical/launchpad/xmlrpc/configure.zcml 2010-11-08 14:16:17 +0000
+++ lib/canonical/launchpad/xmlrpc/configure.zcml 2011-08-15 13:18:36 +0000
@@ -207,4 +207,7 @@
207 <class class="canonical.launchpad.xmlrpc.faults.AccountSuspended">207 <class class="canonical.launchpad.xmlrpc.faults.AccountSuspended">
208 <require like_class="xmlrpclib.Fault" />208 <require like_class="xmlrpclib.Fault" />
209 </class>209 </class>
210 <class class="canonical.launchpad.xmlrpc.faults.InvalidSourcePackageName">
211 <require like_class="xmlrpclib.Fault" />
212 </class>
210</configure>213</configure>
211214
=== modified file 'lib/canonical/launchpad/xmlrpc/faults.py'
--- lib/canonical/launchpad/xmlrpc/faults.py 2010-12-12 23:09:36 +0000
+++ lib/canonical/launchpad/xmlrpc/faults.py 2011-08-15 13:18:36 +0000
@@ -22,6 +22,7 @@
22 'InvalidBranchUniqueName',22 'InvalidBranchUniqueName',
23 'InvalidProductIdentifier',23 'InvalidProductIdentifier',
24 'InvalidBranchUrl',24 'InvalidBranchUrl',
25 'InvalidSourcePackageName',
25 'OopsOccurred',26 'OopsOccurred',
26 'NoBranchWithID',27 'NoBranchWithID',
27 'NoLinkedBranch',28 'NoLinkedBranch',
@@ -476,3 +477,13 @@
476 def __init__(self, server_op, oopsid):477 def __init__(self, server_op, oopsid):
477 LaunchpadFault.__init__(self, server_op=server_op, oopsid=oopsid)478 LaunchpadFault.__init__(self, server_op=server_op, oopsid=oopsid)
478 self.oopsid = oopsid479 self.oopsid = oopsid
480
481
482class InvalidSourcePackageName(LaunchpadFault):
483
484 error_code = 390
485 msg_template = ("%(name)s is not a valid source package name.")
486
487 def __init__(self, name):
488 self.name = name
489 LaunchpadFault.__init__(self, name=name)
479490
=== modified file 'lib/lp/code/tests/helpers.py'
--- lib/lp/code/tests/helpers.py 2011-08-03 11:00:11 +0000
+++ lib/lp/code/tests/helpers.py 2011-08-15 13:18:36 +0000
@@ -6,6 +6,7 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = [7__all__ = [
8 'add_revision_to_branch',8 'add_revision_to_branch',
9 'get_non_existant_source_package_branch_unique_name',
9 'make_erics_fooix_project',10 'make_erics_fooix_project',
10 'make_linked_package_branch',11 'make_linked_package_branch',
11 'make_merge_proposal_without_reviewers',12 'make_merge_proposal_without_reviewers',
@@ -313,3 +314,16 @@
313 for vote in proposal.votes:314 for vote in proposal.votes:
314 removeSecurityProxy(vote).destroySelf()315 removeSecurityProxy(vote).destroySelf()
315 return proposal316 return proposal
317
318
319def get_non_existant_source_package_branch_unique_name(owner, factory):
320 """Return the unique name for a non-existanct source package branch.
321
322 Neither the branch nor the source package name will exist.
323 """
324 distroseries = factory.makeDistroSeries()
325 source_package = factory.getUniqueString('source-package')
326 branch = factory.getUniqueString('branch')
327 return '~%s/%s/%s/%s/%s' % (
328 owner, distroseries.distribution.name, distroseries.name,
329 source_package, branch)
316330
=== modified file 'lib/lp/code/xmlrpc/codehosting.py'
--- lib/lp/code/xmlrpc/codehosting.py 2011-05-25 20:16:58 +0000
+++ lib/lp/code/xmlrpc/codehosting.py 2011-08-15 13:18:36 +0000
@@ -64,6 +64,10 @@
64 LAUNCHPAD_SERVICES,64 LAUNCHPAD_SERVICES,
65 )65 )
66from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch66from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
67from lp.registry.errors import (
68 InvalidName,
69 NoSuchSourcePackageName,
70 )
67from lp.registry.interfaces.person import (71from lp.registry.interfaces.person import (
68 IPersonSet,72 IPersonSet,
69 NoSuchPerson,73 NoSuchPerson,
@@ -72,6 +76,7 @@
72 InvalidProductName,76 InvalidProductName,
73 NoSuchProduct,77 NoSuchProduct,
74 )78 )
79from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
75from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet80from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet
76from lp.services.utils import iter_split81from lp.services.utils import iter_split
7782
@@ -220,6 +225,12 @@
220 except NoSuchProduct, e:225 except NoSuchProduct, e:
221 return faults.NotFound(226 return faults.NotFound(
222 "Project '%s' does not exist." % e.name)227 "Project '%s' does not exist." % e.name)
228 except NoSuchSourcePackageName as e:
229 try:
230 getUtility(ISourcePackageNameSet).new(e.name)
231 except InvalidName:
232 return faults.InvalidSourcePackageName(e.name)
233 return self.createBranch(login_id, branch_path)
223 except NameLookupFailed, e:234 except NameLookupFailed, e:
224 return faults.NotFound(str(e))235 return faults.NotFound(str(e))
225 try:236 try:
226237
=== modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py'
--- lib/lp/code/xmlrpc/tests/test_codehosting.py 2011-08-03 11:00:11 +0000
+++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2011-08-15 13:18:36 +0000
@@ -418,19 +418,39 @@
418 message = "No such distribution series: 'ningnangnong'."418 message = "No such distribution series: 'ningnangnong'."
419 self.assertEqual(faults.NotFound(message), fault)419 self.assertEqual(faults.NotFound(message), fault)
420420
421 def test_createBranch_missing_sourcepackagename(self):
422 # If createBranch is called with the path to a missing source
423 # package, it will create the source package.
424 owner = self.factory.makePerson()
425 distroseries = self.factory.makeDistroSeries()
426 branch_name = self.factory.getUniqueString()
427 unique_name = '/~%s/%s/%s/ningnangnong/%s' % (
428 owner.name, distroseries.distribution.name, distroseries.name,
429 branch_name)
430 branch_id = self.codehosting_api.createBranch(
431 owner.id, escape(unique_name))
432 login(ANONYMOUS)
433 branch = self.branch_lookup.get(branch_id)
434 self.assertEqual(owner, branch.owner)
435 self.assertEqual(distroseries, branch.distroseries)
436 self.assertEqual(
437 'ningnangnong', branch.sourcepackagename.name)
438 self.assertEqual(branch_name, branch.name)
439 self.assertEqual(owner, branch.registrant)
440 self.assertEqual(BranchType.HOSTED, branch.branch_type)
441
421 def test_createBranch_invalid_sourcepackagename(self):442 def test_createBranch_invalid_sourcepackagename(self):
422 # If createBranch is called with the path to an invalid source443 # If createBranch is called with an invalid path, it will fault.
423 # package, it will return a Fault saying so.
424 owner = self.factory.makePerson()444 owner = self.factory.makePerson()
425 distroseries = self.factory.makeDistroSeries()445 distroseries = self.factory.makeDistroSeries()
426 branch_name = self.factory.getUniqueString()446 branch_name = self.factory.getUniqueString()
427 unique_name = '/~%s/%s/%s/ningnangnong/%s' % (447 unique_name = '/~%s/%s/%s/ningn%%20angnong/%s' % (
428 owner.name, distroseries.distribution.name, distroseries.name,448 owner.name, distroseries.distribution.name, distroseries.name,
429 branch_name)449 branch_name)
430 fault = self.codehosting_api.createBranch(450 fault = self.codehosting_api.createBranch(
431 owner.id, escape(unique_name))451 owner.id, escape(unique_name))
432 message = "No such source package: 'ningnangnong'."452 self.assertEqual(
433 self.assertEqual(faults.NotFound(message), fault)453 faults.InvalidSourcePackageName('ningn%20angnong'), fault)
434454
435 def test_createBranch_using_branch_alias(self):455 def test_createBranch_using_branch_alias(self):
436 # Branches can be created using the branch alias and the full unique456 # Branches can be created using the branch alias and the full unique
437457
=== modified file 'lib/lp/codehosting/inmemory.py'
--- lib/lp/codehosting/inmemory.py 2011-08-03 11:00:11 +0000
+++ lib/lp/codehosting/inmemory.py 2011-08-15 13:18:36 +0000
@@ -25,7 +25,10 @@
2525
26from canonical.database.constants import UTC_NOW26from canonical.database.constants import UTC_NOW
27from canonical.launchpad.xmlrpc import faults27from canonical.launchpad.xmlrpc import faults
28from lp.app.validators import LaunchpadValidationError28from lp.app.validators import (
29 LaunchpadValidationError,
30 )
31from lp.app.validators.name import valid_name
29from lp.code.bzr import (32from lp.code.bzr import (
30 BranchFormat,33 BranchFormat,
31 ControlFormat,34 ControlFormat,
@@ -51,6 +54,7 @@
51 ProductBranchTarget,54 ProductBranchTarget,
52 )55 )
53from lp.code.xmlrpc.codehosting import datetime_from_tuple56from lp.code.xmlrpc.codehosting import datetime_from_tuple
57from lp.registry.errors import InvalidName
54from lp.registry.interfaces.pocket import PackagePublishingPocket58from lp.registry.interfaces.pocket import PackagePublishingPocket
55from lp.services.utils import iter_split59from lp.services.utils import iter_split
56from lp.services.xmlrpc import LaunchpadFault60from lp.services.xmlrpc import LaunchpadFault
@@ -194,6 +198,14 @@
194 self.distroseries._linked_branches[self, pocket] = branch198 self.distroseries._linked_branches[self, pocket] = branch
195199
196200
201class SourcePackageNameSet(ObjectSet):
202
203 def new(self, name_string):
204 if not valid_name(name_string):
205 raise InvalidName(name_string)
206 return self._add(FakeSourcePackageName(name_string))
207
208
197@adapter(FakeSourcePackage)209@adapter(FakeSourcePackage)
198@implementer(IBranchTarget)210@implementer(IBranchTarget)
199def fake_source_package_to_branch_target(fake_package):211def fake_source_package_to_branch_target(fake_package):
@@ -454,9 +466,7 @@
454 return distroseries466 return distroseries
455467
456 def makeSourcePackageName(self):468 def makeSourcePackageName(self):
457 sourcepackagename = FakeSourcePackageName(self.getUniqueString())469 return self._sourcepackagename_set.new(self.getUniqueString())
458 self._sourcepackagename_set._add(sourcepackagename)
459 return sourcepackagename
460470
461 def makeSourcePackage(self, distroseries=None, sourcepackagename=None):471 def makeSourcePackage(self, distroseries=None, sourcepackagename=None):
462 if distroseries is None:472 if distroseries is None:
@@ -659,9 +669,12 @@
659 sourcepackagename = self._sourcepackagename_set.getByName(669 sourcepackagename = self._sourcepackagename_set.getByName(
660 data['sourcepackagename'])670 data['sourcepackagename'])
661 if sourcepackagename is None:671 if sourcepackagename is None:
662 raise faults.NotFound(672 try:
663 "No such source package: '%s'."673 sourcepackagename = self._sourcepackagename_set.new(
664 % (data['sourcepackagename'],))674 data['sourcepackagename'])
675 except InvalidName:
676 raise faults.InvalidSourcePackageName(
677 data['sourcepackagename'])
665 sourcepackage = self._factory.makeSourcePackage(678 sourcepackage = self._factory.makeSourcePackage(
666 distroseries, sourcepackagename)679 distroseries, sourcepackagename)
667 else:680 else:
@@ -890,7 +903,7 @@
890 self._product_set = ObjectSet()903 self._product_set = ObjectSet()
891 self._distribution_set = ObjectSet()904 self._distribution_set = ObjectSet()
892 self._distroseries_set = ObjectSet()905 self._distroseries_set = ObjectSet()
893 self._sourcepackagename_set = ObjectSet()906 self._sourcepackagename_set = SourcePackageNameSet()
894 self._factory = FakeObjectFactory(907 self._factory = FakeObjectFactory(
895 self._branch_set, self._person_set, self._product_set,908 self._branch_set, self._person_set, self._product_set,
896 self._distribution_set, self._distroseries_set,909 self._distribution_set, self._distroseries_set,
897910
=== modified file 'lib/lp/codehosting/tests/test_acceptance.py'
--- lib/lp/codehosting/tests/test_acceptance.py 2011-08-12 14:57:27 +0000
+++ lib/lp/codehosting/tests/test_acceptance.py 2011-08-15 13:18:36 +0000
@@ -35,6 +35,9 @@
35from lp.code.enums import BranchType35from lp.code.enums import BranchType
36from lp.code.interfaces.branch import IBranchSet36from lp.code.interfaces.branch import IBranchSet
37from lp.code.interfaces.branchnamespace import get_branch_namespace37from lp.code.interfaces.branchnamespace import get_branch_namespace
38from lp.code.tests.helpers import (
39 get_non_existant_source_package_branch_unique_name,
40 )
38from lp.codehosting import (41from lp.codehosting import (
39 get_bzr_path,42 get_bzr_path,
40 get_BZR_PLUGIN_PATH_for_subprocess,43 get_BZR_PLUGIN_PATH_for_subprocess,
@@ -56,7 +59,7 @@
5659
5760
58class ForkingServerForTests(object):61class ForkingServerForTests(object):
59 """Map starting/stopping a LPForkingService with setUp() and tearDown()."""62 """Map starting/stopping a LPForkingService to setUp() and tearDown()."""
6063
61 def __init__(self):64 def __init__(self):
62 self.process = None65 self.process = None
@@ -68,8 +71,8 @@
68 env = os.environ.copy()71 env = os.environ.copy()
69 env['BZR_PLUGIN_PATH'] = BZR_PLUGIN_PATH72 env['BZR_PLUGIN_PATH'] = BZR_PLUGIN_PATH
70 # TODO: We probably want to use a random disk path for73 # TODO: We probably want to use a random disk path for
71 # forking_daemon_socket, but we need to update config so that the74 # forking_daemon_socket, but we need to update config so that
72 # CodeHosting service can find it.75 # the CodeHosting service can find it.
73 # The main problem is that CodeHostingTac seems to start a tac76 # The main problem is that CodeHostingTac seems to start a tac
74 # server directly from the disk configs, and doesn't use the77 # server directly from the disk configs, and doesn't use the
75 # in-memory config. So we can't just override the memory78 # in-memory config. So we can't just override the memory
@@ -83,14 +86,14 @@
83 self.process = process86 self.process = process
84 # Wait for it to indicate it is running87 # Wait for it to indicate it is running
85 # The first line should be "Preloading" indicating it is ready88 # The first line should be "Preloading" indicating it is ready
86 preloading_line = process.stderr.readline()89 process.stderr.readline()
87 # The next line is the "Listening on socket" line90 # The next line is the "Listening on socket" line
88 socket_line = process.stderr.readline()91 process.stderr.readline()
89 # Now it is ready92 # Now it is ready
9093
91 def tearDown(self):94 def tearDown(self):
92 # SIGTERM is the graceful exit request, potentially we could wait a bit95 # SIGTERM is the graceful exit request, potentially we could wait a
93 # and send something stronger?96 # bit and send something stronger?
94 if self.process is not None and self.process.poll() is None:97 if self.process is not None and self.process.poll() is None:
95 os.kill(self.process.pid, signal.SIGTERM)98 os.kill(self.process.pid, signal.SIGTERM)
96 self.process.wait()99 self.process.wait()
@@ -102,7 +105,6 @@
102 os.remove(self.socket_path)105 os.remove(self.socket_path)
103106
104107
105
106class SSHServerLayer(ZopelessAppServerLayer):108class SSHServerLayer(ZopelessAppServerLayer):
107109
108 _tac_handler = None110 _tac_handler = None
@@ -611,6 +613,15 @@
611 self.local_branch_path, remote_url,613 self.local_branch_path, remote_url,
612 ['Permission denied:', 'Transport operation not possible:'])614 ['Permission denied:', 'Transport operation not possible:'])
613615
616 def test_push_new_branch_of_non_existant_source_package_name(self):
617 ZopelessAppServerLayer.txn.begin()
618 unique_name = get_non_existant_source_package_branch_unique_name(
619 'testuser', self.factory)
620 ZopelessAppServerLayer.txn.commit()
621 remote_url = self.getTransportURL(unique_name)
622 self.push(self.local_branch_path, remote_url)
623 self.assertBranchesMatch(self.local_branch_path, remote_url)
624
614 def test_can_push_loom_branch(self):625 def test_can_push_loom_branch(self):
615 # We can push and pull a loom branch.626 # We can push and pull a loom branch.
616 self.makeLoomBranchAndTree('loom')627 self.makeLoomBranchAndTree('loom')
@@ -696,7 +707,6 @@
696 urllib2.urlopen(web_status_url)707 urllib2.urlopen(web_status_url)
697708
698709
699
700def make_server_tests(base_suite, servers):710def make_server_tests(base_suite, servers):
701 from lp.codehosting.tests.helpers import (711 from lp.codehosting.tests.helpers import (
702 CodeHostingTestProviderAdapter)712 CodeHostingTestProviderAdapter)
703713
=== modified file 'lib/lp/codehosting/vfs/branchfs.py'
--- lib/lp/codehosting/vfs/branchfs.py 2011-08-06 17:37:47 +0000
+++ lib/lp/codehosting/vfs/branchfs.py 2011-08-15 13:18:36 +0000
@@ -46,9 +46,6 @@
46__metaclass__ = type46__metaclass__ = type
47__all__ = [47__all__ = [
48 'AsyncLaunchpadTransport',48 'AsyncLaunchpadTransport',
49 'BadUrlLaunchpad',
50 'BadUrlScheme',
51 'BadUrlSsh',
52 'branch_id_to_path',49 'branch_id_to_path',
53 'DirectDatabaseLaunchpadServer',50 'DirectDatabaseLaunchpadServer',
54 'get_lp_server',51 'get_lp_server',
@@ -599,7 +596,8 @@
599 # exist. You may supply --create-prefix to create all leading596 # exist. You may supply --create-prefix to create all leading
600 # parent directories", which is just misleading.597 # parent directories", which is just misleading.
601 fault = trap_fault(598 fault = trap_fault(
602 fail, faults.NotFound, faults.PermissionDenied)599 fail, faults.NotFound, faults.PermissionDenied,
600 faults.InvalidSourcePackageName)
603 faultString = fault.faultString601 faultString = fault.faultString
604 if isinstance(faultString, unicode):602 if isinstance(faultString, unicode):
605 faultString = faultString.encode('utf-8')603 faultString = faultString.encode('utf-8')
@@ -747,4 +745,3 @@
747 DeferredBlockingProxy(codehosting_client), user_id, branch_transport,745 DeferredBlockingProxy(codehosting_client), user_id, branch_transport,
748 seen_new_branch_hook)746 seen_new_branch_hook)
749 return lp_server747 return lp_server
750
751748
=== modified file 'lib/lp/codehosting/vfs/tests/test_branchfs.py'
--- lib/lp/codehosting/vfs/tests/test_branchfs.py 2011-08-12 05:03:42 +0000
+++ lib/lp/codehosting/vfs/tests/test_branchfs.py 2011-08-15 13:18:36 +0000
@@ -49,7 +49,6 @@
49from twisted.internet import defer49from twisted.internet import defer
5050
51from canonical.launchpad.webapp import errorlog51from canonical.launchpad.webapp import errorlog
52from canonical.launchpad.webapp.errorlog import ErrorReportingUtility
53from canonical.testing.layers import (52from canonical.testing.layers import (
54 ZopelessDatabaseLayer,53 ZopelessDatabaseLayer,
55 )54 )
@@ -798,6 +797,17 @@
798 errors.PermissionDenied, message,797 errors.PermissionDenied, message,
799 transport.mkdir, '~%s/%s/some-name' % (person.name, product.name))798 transport.mkdir, '~%s/%s/some-name' % (person.name, product.name))
800799
800 def test_createBranch_invalid_package_name(self):
801 # When createBranch raises faults.InvalidSourcePackageName, the
802 # transport should translate this to a PermissionDenied exception
803 transport = self.getTransport()
804 series = self.factory.makeDistroSeries()
805 unique_name = '~%s/%s/%s/spaced%%20name/branch' % (
806 self.requester.name, series.distribution.name, series.name)
807 return self.assertFiresFailureWithSubstring(
808 errors.PermissionDenied, "is not a valid source package name",
809 transport.mkdir, unique_name)
810
801 def test_rmdir(self):811 def test_rmdir(self):
802 transport = self.getTransport()812 transport = self.getTransport()
803 self.assertFiresFailure(813 self.assertFiresFailure(
@@ -1155,7 +1165,8 @@
1155 self.addCleanup(memory_server.stop_server)1165 self.addCleanup(memory_server.stop_server)
1156 return memory_server1166 return memory_server
11571167
1158 def _setUpLaunchpadServer(self, user_id, codehosting_api, backing_transport):1168 def _setUpLaunchpadServer(self, user_id, codehosting_api,
1169 backing_transport):
1159 server = LaunchpadServer(1170 server = LaunchpadServer(
1160 XMLRPCWrapper(codehosting_api), user_id, backing_transport)1171 XMLRPCWrapper(codehosting_api), user_id, backing_transport)
1161 server.start_server()1172 server.start_server()
11621173
=== modified file 'lib/lp/registry/errors.py'
--- lib/lp/registry/errors.py 2011-07-27 08:13:18 +0000
+++ lib/lp/registry/errors.py 2011-08-15 13:18:36 +0000
@@ -8,6 +8,7 @@
8 'CannotTransitionToCountryMirror',8 'CannotTransitionToCountryMirror',
9 'CountryMirrorAlreadySet',9 'CountryMirrorAlreadySet',
10 'DeleteSubscriptionError',10 'DeleteSubscriptionError',
11 'InvalidName',
11 'JoinNotAllowed',12 'JoinNotAllowed',
12 'MirrorNotOfficial',13 'MirrorNotOfficial',
13 'MirrorHasNoHTTPURL',14 'MirrorHasNoHTTPURL',
@@ -42,6 +43,10 @@
42 """The name given for a person is already in use by other person."""43 """The name given for a person is already in use by other person."""
4344
4445
46class InvalidName(Exception):
47 """The name given for a person is not valid."""
48
49
45class NoSuchDistroSeries(NameLookupFailed):50class NoSuchDistroSeries(NameLookupFailed):
46 """Raised when we try to find a DistroSeries that doesn't exist."""51 """Raised when we try to find a DistroSeries that doesn't exist."""
47 _message_prefix = "No such distribution series"52 _message_prefix = "No such distribution series"
4853
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2011-07-29 06:08:15 +0000
+++ lib/lp/registry/interfaces/person.py 2011-08-15 13:18:36 +0000
@@ -27,7 +27,6 @@
27 'ITeamCreation',27 'ITeamCreation',
28 'ITeamReassignment',28 'ITeamReassignment',
29 'ImmutableVisibilityError',29 'ImmutableVisibilityError',
30 'InvalidName',
31 'NoSuchPerson',30 'NoSuchPerson',
32 'OPEN_TEAM_POLICY',31 'OPEN_TEAM_POLICY',
33 'PersonCreationRationale',32 'PersonCreationRationale',
@@ -1405,7 +1404,8 @@
1405 )1404 )
1406 @export_factory_operation(Interface, []) # Really IArchive.1405 @export_factory_operation(Interface, []) # Really IArchive.
1407 @operation_for_version("beta")1406 @operation_for_version("beta")
1408 def createPPA(name=None, displayname=None, description=None, private=False):1407 def createPPA(name=None, displayname=None, description=None,
1408 private=False):
1409 """Create a PPA.1409 """Create a PPA.
14101410
1411 :param name: A string with the name of the new PPA to create. If1411 :param name: A string with the name of the new PPA to create. If
@@ -2255,8 +2255,8 @@
2255 addresses associated with.2255 addresses associated with.
22562256
2257 When merging teams, from_person must have no IMailingLists2257 When merging teams, from_person must have no IMailingLists
2258 associated with it. If it has active members they will be deactivated -2258 associated with it. If it has active members they will be deactivated
2259 and reviewer must be supplied.2259 - and reviewer must be supplied.
22602260
2261 :param from_person: An IPerson or ITeam that is a duplicate.2261 :param from_person: An IPerson or ITeam that is a duplicate.
2262 :param to_person: An IPerson or ITeam that is a master.2262 :param to_person: An IPerson or ITeam that is a master.
@@ -2447,10 +2447,6 @@
2447 """A change in team membership visibility is not allowed."""2447 """A change in team membership visibility is not allowed."""
24482448
24492449
2450class InvalidName(Exception):
2451 """The name given for a person is not valid."""
2452
2453
2454class NoSuchPerson(NameLookupFailed):2450class NoSuchPerson(NameLookupFailed):
2455 """Raised when we try to look up an IPerson that doesn't exist."""2451 """Raised when we try to look up an IPerson that doesn't exist."""
24562452
24572453
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2011-08-01 15:28:09 +0000
+++ lib/lp/registry/model/person.py 2011-08-15 13:18:36 +0000
@@ -199,6 +199,7 @@
199 HasRequestedReviewsMixin,199 HasRequestedReviewsMixin,
200 )200 )
201from lp.registry.errors import (201from lp.registry.errors import (
202 InvalidName,
202 JoinNotAllowed,203 JoinNotAllowed,
203 NameAlreadyTaken,204 NameAlreadyTaken,
204 PPACreationError,205 PPACreationError,
@@ -225,7 +226,6 @@
225 )226 )
226from lp.registry.interfaces.person import (227from lp.registry.interfaces.person import (
227 ImmutableVisibilityError,228 ImmutableVisibilityError,
228 InvalidName,
229 IPerson,229 IPerson,
230 IPersonSet,230 IPersonSet,
231 IPersonSettings,231 IPersonSettings,
232232
=== modified file 'lib/lp/registry/model/sourcepackagename.py'
--- lib/lp/registry/model/sourcepackagename.py 2010-11-09 08:38:23 +0000
+++ lib/lp/registry/model/sourcepackagename.py 2011-08-15 13:18:36 +0000
@@ -25,7 +25,11 @@
25 )25 )
26from canonical.launchpad.helpers import ensure_unicode26from canonical.launchpad.helpers import ensure_unicode
27from lp.app.errors import NotFoundError27from lp.app.errors import NotFoundError
28from lp.registry.errors import NoSuchSourcePackageName28from lp.app.validators.name import valid_name
29from lp.registry.errors import (
30 InvalidName,
31 NoSuchSourcePackageName,
32 )
29from lp.registry.interfaces.sourcepackagename import (33from lp.registry.interfaces.sourcepackagename import (
30 ISourcePackageName,34 ISourcePackageName,
31 ISourcePackageNameSet,35 ISourcePackageNameSet,
@@ -90,6 +94,9 @@
90 return SourcePackageName.selectOneBy(name=name)94 return SourcePackageName.selectOneBy(name=name)
9195
92 def new(self, name):96 def new(self, name):
97 if not valid_name(name):
98 raise InvalidName(
99 "%s is not a valid name for a source package." % name)
93 return SourcePackageName(name=name)100 return SourcePackageName(name=name)
94101
95 def getOrCreateByName(self, name):102 def getOrCreateByName(self, name):
96103
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2011-08-03 11:00:11 +0000
+++ lib/lp/registry/tests/test_person.py 2011-08-15 13:18:36 +0000
@@ -47,6 +47,7 @@
47from lp.bugs.model.bug import Bug47from lp.bugs.model.bug import Bug
48from lp.bugs.model.bugtask import get_related_bugtasks_search_params48from lp.bugs.model.bugtask import get_related_bugtasks_search_params
49from lp.registry.errors import (49from lp.registry.errors import (
50 InvalidName,
50 NameAlreadyTaken,51 NameAlreadyTaken,
51 PrivatePersonLinkageError,52 PrivatePersonLinkageError,
52 )53 )
@@ -55,7 +56,6 @@
55from lp.registry.interfaces.nameblacklist import INameBlacklistSet56from lp.registry.interfaces.nameblacklist import INameBlacklistSet
56from lp.registry.interfaces.person import (57from lp.registry.interfaces.person import (
57 ImmutableVisibilityError,58 ImmutableVisibilityError,
58 InvalidName,
59 IPersonSet,59 IPersonSet,
60 PersonCreationRationale,60 PersonCreationRationale,
61 PersonVisibility,61 PersonVisibility,
6262
=== added file 'lib/lp/registry/tests/test_sourcepackagename.py'
--- lib/lp/registry/tests/test_sourcepackagename.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/tests/test_sourcepackagename.py 2011-08-15 13:18:36 +0000
@@ -0,0 +1,24 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for SourcePackageName"""
5
6__metaclass__ = type
7
8from testtools.testcase import ExpectedException
9
10from canonical.testing.layers import DatabaseLayer
11from lp.registry.errors import InvalidName
12from lp.registry.model.sourcepackagename import SourcePackageNameSet
13from lp.testing import TestCase
14
15
16class TestSourcePackageNameSet(TestCase):
17
18 layer = DatabaseLayer
19
20 def test_invalid_name(self):
21 with ExpectedException(
22 InvalidName,
23 'invalid%20name is not a valid name for a source package.'):
24 SourcePackageNameSet().new('invalid%20name')