launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #17905
Re: [Merge] lp:~cjwatson/launchpad/git-testing into lp:launchpad
Review: Approve code
Diff comments:
> === 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:43:03 +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:43:03 +0000
> @@ -2,7 +2,7 @@
> # NOTE: The first line above must stay first; do not move the copyright
> # notice to the top. See http://www.python.org/dev/peps/pep-0263/.
> #
> -# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
> +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
> """Testing infrastructure for the Launchpad application.
> @@ -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
Any reason to take the split arguments rather than just target?
> +
> + 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)
Are all these target-specific helpers worth it? I haven't checked how extensively the Branch equivalents are used.
> +
> 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,
>
--
https://code.launchpad.net/~cjwatson/launchpad/git-testing/+merge/249840
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
References