launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #17882
[Merge] lp:~cjwatson/launchpad/git-testing into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/git-testing into lp:launchpad with lp:~cjwatson/launchpad/git-sharing as a prerequisite.
Commit message:
Add initial Git repository testing support, and a first batch of tests for GitRepository itself.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1032731 in Launchpad itself: "Support for Launchpad-hosted Git repositories"
https://bugs.launchpad.net/launchpad/+bug/1032731
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-testing/+merge/249840
Add initial Git repository testing support, and a first batch of tests for GitRepository itself.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-testing into lp:launchpad.
=== added file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2015-02-16 15:41:09 +0000
@@ -0,0 +1,389 @@
+# Copyright 2015 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for Git repositories."""
+
+__metaclass__ = type
+
+from datetime import datetime
+
+from lazr.lifecycle.event import ObjectModifiedEvent
+import pytz
+from zope.component import getUtility
+from zope.event import notify
+from zope.security.proxy import removeSecurityProxy
+
+from lp.app.enums import (
+ InformationType,
+ PRIVATE_INFORMATION_TYPES,
+ PUBLIC_INFORMATION_TYPES,
+ )
+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.code.errors import (
+ GitRepositoryCreatorNotMemberOfOwnerTeam,
+ GitRepositoryCreatorNotOwner,
+ GitTargetError,
+ )
+from lp.code.interfaces.gitnamespace import (
+ IGitNamespacePolicy,
+ IGitNamespaceSet,
+ )
+from lp.code.interfaces.gitrepository import IGitRepository
+from lp.registry.enums import BranchSharingPolicy
+from lp.services.database.constants import UTC_NOW
+from lp.services.webapp.authorization import check_permission
+from lp.testing import (
+ admin_logged_in,
+ celebrity_logged_in,
+ person_logged_in,
+ TestCaseWithFactory,
+ verifyObject,
+ )
+from lp.testing.layers import DatabaseFunctionalLayer
+
+
+class TestGitRepository(TestCaseWithFactory):
+ """Test basic properties about Launchpad database Git repositories."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_implements_IGitRepository(self):
+ repository = self.factory.makeGitRepository()
+ verifyObject(IGitRepository, repository)
+
+ def test_unique_name_project(self):
+ project = self.factory.makeProduct()
+ repository = self.factory.makeProjectGitRepository(project=project)
+ self.assertEqual(
+ "~%s/%s/+git/%s" % (
+ repository.owner.name, project.name, repository.name),
+ repository.unique_name)
+
+ def test_unique_name_package(self):
+ dsp = self.factory.makeDistributionSourcePackage()
+ repository = self.factory.makePackageGitRepository(
+ distro_source_package=dsp)
+ self.assertEqual(
+ "~%s/%s/+source/%s/+git/%s" % (
+ repository.owner.name, dsp.distribution.name,
+ dsp.sourcepackagename.name, repository.name),
+ repository.unique_name)
+
+ def test_unique_name_personal(self):
+ repository = self.factory.makePersonalGitRepository()
+ self.assertEqual(
+ "~%s/+git/%s" % (repository.owner.name, repository.name),
+ repository.unique_name)
+
+ def test_target_project(self):
+ project = self.factory.makeProduct()
+ repository = self.factory.makeProjectGitRepository(project=project)
+ self.assertEqual(project, repository.target)
+
+ def test_target_package(self):
+ dsp = self.factory.makeDistributionSourcePackage()
+ repository = self.factory.makePackageGitRepository(
+ distro_source_package=dsp)
+ self.assertEqual(dsp, repository.target)
+
+ def test_target_personal(self):
+ repository = self.factory.makePersonalGitRepository()
+ self.assertEqual(repository.owner, repository.target)
+
+
+class TestGitIdentityMixin(TestCaseWithFactory):
+ """Test the defaults and identities provided by GitIdentityMixin."""
+
+ layer = DatabaseFunctionalLayer
+
+ def assertGitIdentity(self, repository, identity_path):
+ """Assert that the Git identity of 'repository' is 'identity_path'.
+
+ Actually, it'll be lp:<identity_path>.
+ """
+ self.assertEqual(
+ identity_path, repository.shortened_path, "shortened path")
+ self.assertEqual(
+ "lp:%s" % identity_path, repository.git_identity, "git identity")
+
+ def test_git_identity_default(self):
+ # By default, the Git identity is the repository's unique name.
+ repository = self.factory.makeAnyGitRepository()
+ self.assertGitIdentity(repository, repository.unique_name)
+
+ def test_identities_no_defaults(self):
+ # If there are no defaults, the only repository identity is the
+ # unique name.
+ repository = self.factory.makeAnyGitRepository()
+ self.assertEqual(
+ [(repository.unique_name, repository)],
+ repository.getRepositoryIdentities())
+
+ # XXX cjwatson 2015-02-12: This will need to be expanded once support
+ # for default repositories is in place.
+
+
+class TestGitRepositoryDateLastModified(TestCaseWithFactory):
+ """Exercise the situations where date_last_modified is updated."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_initial_value(self):
+ # The initial value of date_last_modified is date_created.
+ repository = self.factory.makeAnyGitRepository()
+ self.assertEqual(
+ repository.date_created, repository.date_last_modified)
+
+ def test_modifiedevent_sets_date_last_modified(self):
+ # When a GitRepository receives an object modified event, the last
+ # modified date is set to UTC_NOW.
+ repository = self.factory.makeAnyGitRepository(
+ date_created=datetime(2015, 02, 04, 17, 42, 0, tzinfo=pytz.UTC))
+ notify(ObjectModifiedEvent(
+ removeSecurityProxy(repository), repository,
+ [IGitRepository["name"]]))
+ self.assertSqlAttributeEqualsDate(
+ repository, "date_last_modified", UTC_NOW)
+
+ # XXX cjwatson 2015-02-04: This will need to be expanded once Launchpad
+ # actually notices any interesting kind of repository modifications.
+
+
+class TestCodebrowse(TestCaseWithFactory):
+ """Tests for Git repository codebrowse support."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_simple(self):
+ # The basic codebrowse URL for a repository is an 'https' URL.
+ repository = self.factory.makeAnyGitRepository()
+ self.assertEqual(
+ "https://git.launchpad.dev/" + repository.unique_name,
+ repository.getCodebrowseUrl())
+
+
+class TestGitRepositoryNamespace(TestCaseWithFactory):
+ """Test `IGitRepository.namespace`."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_namespace_personal(self):
+ # The namespace attribute of a personal repository points to the
+ # namespace that corresponds to ~owner.
+ repository = self.factory.makePersonalGitRepository()
+ namespace = getUtility(IGitNamespaceSet).get(person=repository.owner)
+ self.assertEqual(namespace, repository.namespace)
+
+ def test_namespace_project(self):
+ # The namespace attribute of a project repository points to the
+ # namespace that corresponds to ~owner/project.
+ project = self.factory.makeProduct()
+ repository = self.factory.makeProjectGitRepository(project=project)
+ namespace = getUtility(IGitNamespaceSet).get(
+ person=repository.owner, project=project)
+ self.assertEqual(namespace, repository.namespace)
+
+ def test_namespace_package(self):
+ # The namespace attribute of a package repository points to the
+ # namespace that corresponds to
+ # ~owner/distribution/+source/sourcepackagename.
+ dsp = self.factory.makeDistributionSourcePackage()
+ repository = self.factory.makePackageGitRepository(
+ distro_source_package=dsp)
+ namespace = getUtility(IGitNamespaceSet).get(
+ person=repository.owner, distribution=dsp.distribution,
+ sourcepackagename=dsp.sourcepackagename)
+ self.assertEqual(namespace, repository.namespace)
+
+
+class TestGitRepositoryGetAllowedInformationTypes(TestCaseWithFactory):
+ """Test `IGitRepository.getAllowedInformationTypes`."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_normal_user_sees_namespace_types(self):
+ # An unprivileged user sees the types allowed by the namespace.
+ repository = self.factory.makeGitRepository()
+ policy = IGitNamespacePolicy(repository.namespace)
+ self.assertContentEqual(
+ policy.getAllowedInformationTypes(),
+ repository.getAllowedInformationTypes(repository.owner))
+ self.assertNotIn(
+ InformationType.PROPRIETARY,
+ repository.getAllowedInformationTypes(repository.owner))
+ self.assertNotIn(
+ InformationType.EMBARGOED,
+ repository.getAllowedInformationTypes(repository.owner))
+
+ def test_admin_sees_namespace_types(self):
+ # An admin sees all the types, since they occasionally need to
+ # override the namespace rules. This is hopefully temporary, and
+ # can go away once the new sharing rules (granting non-commercial
+ # projects limited use of private repositories) are deployed.
+ repository = self.factory.makeGitRepository()
+ admin = self.factory.makeAdministrator()
+ self.assertContentEqual(
+ PUBLIC_INFORMATION_TYPES + PRIVATE_INFORMATION_TYPES,
+ repository.getAllowedInformationTypes(admin))
+ self.assertIn(
+ InformationType.PROPRIETARY,
+ repository.getAllowedInformationTypes(admin))
+
+
+class TestGitRepositoryModerate(TestCaseWithFactory):
+ """Test that project owners and commercial admins can moderate Git
+ repositories."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_moderate_permission(self):
+ # Test the ModerateGitRepository security checker.
+ project = self.factory.makeProduct()
+ repository = self.factory.makeProjectGitRepository(project=project)
+ with person_logged_in(project.owner):
+ self.assertTrue(check_permission("launchpad.Moderate", repository))
+ with celebrity_logged_in("commercial_admin"):
+ self.assertTrue(check_permission("launchpad.Moderate", repository))
+ with person_logged_in(self.factory.makePerson()):
+ self.assertFalse(
+ check_permission("launchpad.Moderate", repository))
+
+ def test_attribute_smoketest(self):
+ # Users with launchpad.Moderate can set attributes.
+ project = self.factory.makeProduct()
+ repository = self.factory.makeProjectGitRepository(project=project)
+ with person_logged_in(project.owner):
+ repository.name = u"not-secret"
+ self.assertEqual(u"not-secret", repository.name)
+
+
+class TestGitRepositorySetOwner(TestCaseWithFactory):
+ """Test `IGitRepository.setOwner`."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_owner_sets_team(self):
+ # The owner of the repository can set the owner of the repository to
+ # be a team they are a member of.
+ repository = self.factory.makeAnyGitRepository()
+ team = self.factory.makeTeam(owner=repository.owner)
+ with person_logged_in(repository.owner):
+ repository.setOwner(team, repository.owner)
+ self.assertEqual(team, repository.owner)
+
+ def test_owner_cannot_set_nonmember_team(self):
+ # The owner of the repository cannot set the owner to be a team they
+ # are not a member of.
+ repository = self.factory.makeAnyGitRepository()
+ team = self.factory.makeTeam()
+ with person_logged_in(repository.owner):
+ self.assertRaises(
+ GitRepositoryCreatorNotMemberOfOwnerTeam,
+ repository.setOwner, team, repository.owner)
+
+ def test_owner_cannot_set_other_user(self):
+ # The owner of the repository cannot set the new owner to be another
+ # person.
+ repository = self.factory.makeAnyGitRepository()
+ person = self.factory.makePerson()
+ with person_logged_in(repository.owner):
+ self.assertRaises(
+ GitRepositoryCreatorNotOwner,
+ repository.setOwner, person, repository.owner)
+
+ def test_admin_can_set_any_team_or_person(self):
+ # A Launchpad admin can set the repository to be owned by any team
+ # or person.
+ repository = self.factory.makeAnyGitRepository()
+ team = self.factory.makeTeam()
+ # To get a random administrator, choose the admin team owner.
+ admin = getUtility(ILaunchpadCelebrities).admin.teamowner
+ with person_logged_in(admin):
+ repository.setOwner(team, admin)
+ self.assertEqual(team, repository.owner)
+ person = self.factory.makePerson()
+ repository.setOwner(person, admin)
+ self.assertEqual(person, repository.owner)
+
+
+class TestGitRepositorySetTarget(TestCaseWithFactory):
+ """Test `IGitRepository.setTarget`."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_personal_to_project(self):
+ # A personal repository can be moved to a project.
+ repository = self.factory.makePersonalGitRepository()
+ project = self.factory.makeProduct()
+ with person_logged_in(repository.owner):
+ repository.setTarget(target=project, user=repository.owner)
+ self.assertEqual(project, repository.target)
+
+ def test_personal_to_package(self):
+ # A personal repository can be moved to a package.
+ repository = self.factory.makePersonalGitRepository()
+ dsp = self.factory.makeDistributionSourcePackage()
+ with person_logged_in(repository.owner):
+ repository.setTarget(target=dsp, user=repository.owner)
+ self.assertEqual(dsp, repository.target)
+
+ def test_project_to_other_project(self):
+ # Move a repository from one project to another.
+ repository = self.factory.makeProjectGitRepository()
+ project = self.factory.makeProduct()
+ with person_logged_in(repository.owner):
+ repository.setTarget(target=project, user=repository.owner)
+ self.assertEqual(project, repository.target)
+
+ def test_project_to_package(self):
+ # Move a repository from a project to a package.
+ repository = self.factory.makeProjectGitRepository()
+ dsp = self.factory.makeDistributionSourcePackage()
+ with person_logged_in(repository.owner):
+ repository.setTarget(target=dsp, user=repository.owner)
+ self.assertEqual(dsp, repository.target)
+
+ def test_project_to_personal(self):
+ # Move a repository from a project to a personal namespace.
+ owner = self.factory.makePerson()
+ repository = self.factory.makeProjectGitRepository(owner=owner)
+ with person_logged_in(owner):
+ repository.setTarget(target=owner, user=owner)
+ self.assertEqual(owner, repository.target)
+
+ def test_package_to_other_package(self):
+ # Move a repository from one package to another.
+ repository = self.factory.makePackageGitRepository()
+ dsp = self.factory.makeDistributionSourcePackage()
+ with person_logged_in(repository.owner):
+ repository.setTarget(target=dsp, user=repository.owner)
+ self.assertEqual(dsp, repository.target)
+
+ def test_package_to_project(self):
+ # Move a repository from a package to a project.
+ repository = self.factory.makePackageGitRepository()
+ project = self.factory.makeProduct()
+ with person_logged_in(repository.owner):
+ repository.setTarget(target=project, user=repository.owner)
+ self.assertEqual(project, repository.target)
+
+ def test_package_to_personal(self):
+ # Move a repository from a package to a personal namespace.
+ owner = self.factory.makePerson()
+ repository = self.factory.makePackageGitRepository(owner=owner)
+ with person_logged_in(owner):
+ repository.setTarget(target=owner, user=owner)
+ self.assertEqual(owner, repository.target)
+
+ def test_public_to_proprietary_only_project(self):
+ # A repository cannot be moved to a target where the sharing policy does
+ # not allow it.
+ owner = self.factory.makePerson()
+ commercial_project = self.factory.makeProduct(
+ owner=owner, branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ repository = self.factory.makeGitRepository(
+ owner=owner, information_type=InformationType.PUBLIC)
+ with admin_logged_in():
+ self.assertRaises(
+ GitTargetError, repository.setTarget,
+ target=commercial_project, user=owner)
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2015-01-29 16:28:30 +0000
+++ lib/lp/testing/factory.py 2015-02-16 15:41:09 +0000
@@ -118,6 +118,7 @@
from lp.code.interfaces.codeimportevent import ICodeImportEventSet
from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
from lp.code.interfaces.codeimportresult import ICodeImportResultSet
+from lp.code.interfaces.gitnamespace import get_git_namespace
from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
from lp.code.interfaces.revision import IRevisionSet
from lp.code.interfaces.sourcepackagerecipe import (
@@ -1668,6 +1669,93 @@
revision_date=revision_date)
return branch.createBranchRevision(sequence, revision)
+ def makeGitRepository(self, owner=None, project=_DEFAULT,
+ distro_source_package=None, registrant=None,
+ name=None, information_type=None,
+ **optional_repository_args):
+ """Create and return a new, arbitrary GitRepository.
+
+ Any parameters for `IGitNamespace.createRepository` can be specified
+ to override the default ones.
+ """
+ if owner is None:
+ owner = self.makePerson()
+ if name is None:
+ name = self.getUniqueString('gitrepository').decode('utf-8')
+
+ if distro_source_package is None:
+ if project is _DEFAULT:
+ project = self.makeProduct()
+ target = project
+ else:
+ assert project is _DEFAULT, (
+ "Passed both distribution source package and project details")
+ target = distro_source_package
+
+ if registrant is None:
+ if owner.is_team:
+ registrant = removeSecurityProxy(owner).teamowner
+ else:
+ registrant = owner
+
+ namespace = get_git_namespace(target, owner)
+ repository = namespace.createRepository(
+ registrant=registrant, name=name, **optional_repository_args)
+ naked_repository = removeSecurityProxy(repository)
+ if information_type is not None:
+ naked_repository.transitionToInformationType(
+ information_type, registrant, verify_policy=False)
+ return repository
+
+ def makePackageGitRepository(self, distro_source_package=None,
+ distribution=None, sourcepackagename=None,
+ **kwargs):
+ """Make a package Git repository on an arbitrary package.
+
+ See `makeGitRepository` for more information on arguments.
+
+ You can pass in either `distro_source_package` or one or both of
+ `distribution` and `sourcepackagename`, but not combinations or all
+ of them.
+ """
+ assert not(distro_source_package is not None and
+ distribution is not None), (
+ "Don't pass in both distro_source_package and distribution")
+ assert not(distro_source_package is not None
+ and sourcepackagename is not None), (
+ "Don't pass in both distro_source_package and sourcepackagename")
+ if distro_source_package is None:
+ distro_source_package = self.makeDistributionSourcePackage(
+ distribution=distribution, sourcepackagename=sourcepackagename)
+ return self.makeGitRepository(
+ distro_source_package=distro_source_package, **kwargs)
+
+ def makePersonalGitRepository(self, owner=None, **kwargs):
+ """Make a personal Git repository on an arbitrary person.
+
+ See `makeGitRepository` for more information on arguments.
+ """
+ if owner is None:
+ owner = self.makePerson()
+ return self.makeGitRepository(
+ owner=owner, project=None, distro_source_package=None, **kwargs)
+
+ def makeProjectGitRepository(self, project=None, **kwargs):
+ """Make a project Git repository on an arbitrary project.
+
+ See `makeGitRepository` for more information on arguments.
+ """
+ if project is None:
+ project = self.makeProduct()
+ return self.makeGitRepository(project=project, **kwargs)
+
+ def makeAnyGitRepository(self, **kwargs):
+ """Make a Git repository without caring about its container.
+
+ See `makeGitRepository` for more information on arguments.
+ """
+ return self.makeProjectGitRepository(**kwargs)
+
def makeBug(self, target=None, owner=None, bug_watch_url=None,
information_type=None, date_closed=None, title=None,
date_created=None, description=None, comment=None,
Follow ups