Merge lp:~cjwatson/launchpad/git-testing into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: no longer in the source branch.
Merged at revision: 17356
Proposed branch: lp:~cjwatson/launchpad/git-testing
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/git-sharing
Diff against target: 457 lines (+427/-1)
2 files modified
lib/lp/code/model/tests/test_gitrepository.py (+394/-0)
lib/lp/testing/factory.py (+33/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/git-testing
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+249840@code.launchpad.net

Commit message

Add initial Git repository testing support, and a first batch of tests for GitRepository itself.

Description of the change

Add initial Git repository testing support, and a first batch of tests for GitRepository itself.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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-20 00:57:45 +0000
@@ -0,0 +1,394 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for Git repositories."""
5
6__metaclass__ = type
7
8from datetime import datetime
9
10from lazr.lifecycle.event import ObjectModifiedEvent
11import pytz
12from zope.component import getUtility
13from zope.event import notify
14from zope.security.proxy import removeSecurityProxy
15
16from lp.app.enums import (
17 InformationType,
18 PRIVATE_INFORMATION_TYPES,
19 PUBLIC_INFORMATION_TYPES,
20 )
21from lp.app.interfaces.launchpad import ILaunchpadCelebrities
22from lp.code.errors import (
23 GitRepositoryCreatorNotMemberOfOwnerTeam,
24 GitRepositoryCreatorNotOwner,
25 GitTargetError,
26 )
27from lp.code.interfaces.gitnamespace import (
28 IGitNamespacePolicy,
29 IGitNamespaceSet,
30 )
31from lp.code.interfaces.gitrepository import IGitRepository
32from lp.registry.enums import BranchSharingPolicy
33from lp.services.database.constants import UTC_NOW
34from lp.services.webapp.authorization import check_permission
35from lp.testing import (
36 admin_logged_in,
37 celebrity_logged_in,
38 person_logged_in,
39 TestCaseWithFactory,
40 verifyObject,
41 )
42from lp.testing.layers import DatabaseFunctionalLayer
43
44
45class TestGitRepository(TestCaseWithFactory):
46 """Test basic properties about Launchpad database Git repositories."""
47
48 layer = DatabaseFunctionalLayer
49
50 def test_implements_IGitRepository(self):
51 repository = self.factory.makeGitRepository()
52 verifyObject(IGitRepository, repository)
53
54 def test_unique_name_project(self):
55 project = self.factory.makeProduct()
56 repository = self.factory.makeGitRepository(target=project)
57 self.assertEqual(
58 "~%s/%s/+git/%s" % (
59 repository.owner.name, project.name, repository.name),
60 repository.unique_name)
61
62 def test_unique_name_package(self):
63 dsp = self.factory.makeDistributionSourcePackage()
64 repository = self.factory.makeGitRepository(target=dsp)
65 self.assertEqual(
66 "~%s/%s/+source/%s/+git/%s" % (
67 repository.owner.name, dsp.distribution.name,
68 dsp.sourcepackagename.name, repository.name),
69 repository.unique_name)
70
71 def test_unique_name_personal(self):
72 owner = self.factory.makePerson()
73 repository = self.factory.makeGitRepository(owner=owner, target=owner)
74 self.assertEqual(
75 "~%s/+git/%s" % (owner.name, repository.name),
76 repository.unique_name)
77
78 def test_target_project(self):
79 project = self.factory.makeProduct()
80 repository = self.factory.makeGitRepository(target=project)
81 self.assertEqual(project, repository.target)
82
83 def test_target_package(self):
84 dsp = self.factory.makeDistributionSourcePackage()
85 repository = self.factory.makeGitRepository(target=dsp)
86 self.assertEqual(dsp, repository.target)
87
88 def test_target_personal(self):
89 owner = self.factory.makePerson()
90 repository = self.factory.makeGitRepository(owner=owner, target=owner)
91 self.assertEqual(owner, repository.target)
92
93
94class TestGitIdentityMixin(TestCaseWithFactory):
95 """Test the defaults and identities provided by GitIdentityMixin."""
96
97 layer = DatabaseFunctionalLayer
98
99 def assertGitIdentity(self, repository, identity_path):
100 """Assert that the Git identity of 'repository' is 'identity_path'.
101
102 Actually, it'll be lp:<identity_path>.
103 """
104 self.assertEqual(
105 identity_path, repository.shortened_path, "shortened path")
106 self.assertEqual(
107 "lp:%s" % identity_path, repository.git_identity, "git identity")
108
109 def test_git_identity_default(self):
110 # By default, the Git identity is the repository's unique name.
111 repository = self.factory.makeGitRepository()
112 self.assertGitIdentity(repository, repository.unique_name)
113
114 def test_identities_no_defaults(self):
115 # If there are no defaults, the only repository identity is the
116 # unique name.
117 repository = self.factory.makeGitRepository()
118 self.assertEqual(
119 [(repository.unique_name, repository)],
120 repository.getRepositoryIdentities())
121
122 # XXX cjwatson 2015-02-12: This will need to be expanded once support
123 # for default repositories is in place.
124
125
126class TestGitRepositoryDateLastModified(TestCaseWithFactory):
127 """Exercise the situations where date_last_modified is updated."""
128
129 layer = DatabaseFunctionalLayer
130
131 def test_initial_value(self):
132 # The initial value of date_last_modified is date_created.
133 repository = self.factory.makeGitRepository()
134 self.assertEqual(
135 repository.date_created, repository.date_last_modified)
136
137 def test_modifiedevent_sets_date_last_modified(self):
138 # When a GitRepository receives an object modified event, the last
139 # modified date is set to UTC_NOW.
140 repository = self.factory.makeGitRepository(
141 date_created=datetime(2015, 02, 04, 17, 42, 0, tzinfo=pytz.UTC))
142 notify(ObjectModifiedEvent(
143 removeSecurityProxy(repository), repository,
144 [IGitRepository["name"]]))
145 self.assertSqlAttributeEqualsDate(
146 repository, "date_last_modified", UTC_NOW)
147
148 # XXX cjwatson 2015-02-04: This will need to be expanded once Launchpad
149 # actually notices any interesting kind of repository modifications.
150
151
152class TestCodebrowse(TestCaseWithFactory):
153 """Tests for Git repository codebrowse support."""
154
155 layer = DatabaseFunctionalLayer
156
157 def test_simple(self):
158 # The basic codebrowse URL for a repository is an 'https' URL.
159 repository = self.factory.makeGitRepository()
160 self.assertEqual(
161 "https://git.launchpad.dev/" + repository.unique_name,
162 repository.getCodebrowseUrl())
163
164
165class TestGitRepositoryNamespace(TestCaseWithFactory):
166 """Test `IGitRepository.namespace`."""
167
168 layer = DatabaseFunctionalLayer
169
170 def test_namespace_personal(self):
171 # The namespace attribute of a personal repository points to the
172 # namespace that corresponds to ~owner.
173 owner = self.factory.makePerson()
174 repository = self.factory.makeGitRepository(owner=owner, target=owner)
175 namespace = getUtility(IGitNamespaceSet).get(person=owner)
176 self.assertEqual(namespace, repository.namespace)
177
178 def test_namespace_project(self):
179 # The namespace attribute of a project repository points to the
180 # namespace that corresponds to ~owner/project.
181 project = self.factory.makeProduct()
182 repository = self.factory.makeGitRepository(target=project)
183 namespace = getUtility(IGitNamespaceSet).get(
184 person=repository.owner, project=project)
185 self.assertEqual(namespace, repository.namespace)
186
187 def test_namespace_package(self):
188 # The namespace attribute of a package repository points to the
189 # namespace that corresponds to
190 # ~owner/distribution/+source/sourcepackagename.
191 dsp = self.factory.makeDistributionSourcePackage()
192 repository = self.factory.makeGitRepository(target=dsp)
193 namespace = getUtility(IGitNamespaceSet).get(
194 person=repository.owner, distribution=dsp.distribution,
195 sourcepackagename=dsp.sourcepackagename)
196 self.assertEqual(namespace, repository.namespace)
197
198
199class TestGitRepositoryGetAllowedInformationTypes(TestCaseWithFactory):
200 """Test `IGitRepository.getAllowedInformationTypes`."""
201
202 layer = DatabaseFunctionalLayer
203
204 def test_normal_user_sees_namespace_types(self):
205 # An unprivileged user sees the types allowed by the namespace.
206 repository = self.factory.makeGitRepository()
207 policy = IGitNamespacePolicy(repository.namespace)
208 self.assertContentEqual(
209 policy.getAllowedInformationTypes(),
210 repository.getAllowedInformationTypes(repository.owner))
211 self.assertNotIn(
212 InformationType.PROPRIETARY,
213 repository.getAllowedInformationTypes(repository.owner))
214 self.assertNotIn(
215 InformationType.EMBARGOED,
216 repository.getAllowedInformationTypes(repository.owner))
217
218 def test_admin_sees_namespace_types(self):
219 # An admin sees all the types, since they occasionally need to
220 # override the namespace rules. This is hopefully temporary, and
221 # can go away once the new sharing rules (granting non-commercial
222 # projects limited use of private repositories) are deployed.
223 repository = self.factory.makeGitRepository()
224 admin = self.factory.makeAdministrator()
225 self.assertContentEqual(
226 PUBLIC_INFORMATION_TYPES + PRIVATE_INFORMATION_TYPES,
227 repository.getAllowedInformationTypes(admin))
228 self.assertIn(
229 InformationType.PROPRIETARY,
230 repository.getAllowedInformationTypes(admin))
231
232
233class TestGitRepositoryModerate(TestCaseWithFactory):
234 """Test that project owners and commercial admins can moderate Git
235 repositories."""
236
237 layer = DatabaseFunctionalLayer
238
239 def test_moderate_permission(self):
240 # Test the ModerateGitRepository security checker.
241 project = self.factory.makeProduct()
242 repository = self.factory.makeGitRepository(target=project)
243 with person_logged_in(project.owner):
244 self.assertTrue(check_permission("launchpad.Moderate", repository))
245 with celebrity_logged_in("commercial_admin"):
246 self.assertTrue(check_permission("launchpad.Moderate", repository))
247 with person_logged_in(self.factory.makePerson()):
248 self.assertFalse(
249 check_permission("launchpad.Moderate", repository))
250
251 def test_attribute_smoketest(self):
252 # Users with launchpad.Moderate can set attributes.
253 project = self.factory.makeProduct()
254 repository = self.factory.makeGitRepository(target=project)
255 with person_logged_in(project.owner):
256 repository.name = u"not-secret"
257 self.assertEqual(u"not-secret", repository.name)
258
259
260class TestGitRepositorySetOwner(TestCaseWithFactory):
261 """Test `IGitRepository.setOwner`."""
262
263 layer = DatabaseFunctionalLayer
264
265 def test_owner_sets_team(self):
266 # The owner of the repository can set the owner of the repository to
267 # be a team they are a member of.
268 repository = self.factory.makeGitRepository()
269 team = self.factory.makeTeam(owner=repository.owner)
270 with person_logged_in(repository.owner):
271 repository.setOwner(team, repository.owner)
272 self.assertEqual(team, repository.owner)
273
274 def test_owner_cannot_set_nonmember_team(self):
275 # The owner of the repository cannot set the owner to be a team they
276 # are not a member of.
277 repository = self.factory.makeGitRepository()
278 team = self.factory.makeTeam()
279 with person_logged_in(repository.owner):
280 self.assertRaises(
281 GitRepositoryCreatorNotMemberOfOwnerTeam,
282 repository.setOwner, team, repository.owner)
283
284 def test_owner_cannot_set_other_user(self):
285 # The owner of the repository cannot set the new owner to be another
286 # person.
287 repository = self.factory.makeGitRepository()
288 person = self.factory.makePerson()
289 with person_logged_in(repository.owner):
290 self.assertRaises(
291 GitRepositoryCreatorNotOwner,
292 repository.setOwner, person, repository.owner)
293
294 def test_admin_can_set_any_team_or_person(self):
295 # A Launchpad admin can set the repository to be owned by any team
296 # or person.
297 repository = self.factory.makeGitRepository()
298 team = self.factory.makeTeam()
299 # To get a random administrator, choose the admin team owner.
300 admin = getUtility(ILaunchpadCelebrities).admin.teamowner
301 with person_logged_in(admin):
302 repository.setOwner(team, admin)
303 self.assertEqual(team, repository.owner)
304 person = self.factory.makePerson()
305 repository.setOwner(person, admin)
306 self.assertEqual(person, repository.owner)
307
308
309class TestGitRepositorySetTarget(TestCaseWithFactory):
310 """Test `IGitRepository.setTarget`."""
311
312 layer = DatabaseFunctionalLayer
313
314 def test_personal_to_project(self):
315 # A personal repository can be moved to a project.
316 owner = self.factory.makePerson()
317 repository = self.factory.makeGitRepository(owner=owner, target=owner)
318 project = self.factory.makeProduct()
319 with person_logged_in(owner):
320 repository.setTarget(target=project, user=owner)
321 self.assertEqual(project, repository.target)
322
323 def test_personal_to_package(self):
324 # A personal repository can be moved to a package.
325 owner = self.factory.makePerson()
326 repository = self.factory.makeGitRepository(owner=owner, target=owner)
327 dsp = self.factory.makeDistributionSourcePackage()
328 with person_logged_in(owner):
329 repository.setTarget(target=dsp, user=owner)
330 self.assertEqual(dsp, repository.target)
331
332 def test_project_to_other_project(self):
333 # Move a repository from one project to another.
334 repository = self.factory.makeGitRepository()
335 project = self.factory.makeProduct()
336 with person_logged_in(repository.owner):
337 repository.setTarget(target=project, user=repository.owner)
338 self.assertEqual(project, repository.target)
339
340 def test_project_to_package(self):
341 # Move a repository from a project to a package.
342 repository = self.factory.makeGitRepository()
343 dsp = self.factory.makeDistributionSourcePackage()
344 with person_logged_in(repository.owner):
345 repository.setTarget(target=dsp, user=repository.owner)
346 self.assertEqual(dsp, repository.target)
347
348 def test_project_to_personal(self):
349 # Move a repository from a project to a personal namespace.
350 owner = self.factory.makePerson()
351 repository = self.factory.makeGitRepository(owner=owner)
352 with person_logged_in(owner):
353 repository.setTarget(target=owner, user=owner)
354 self.assertEqual(owner, repository.target)
355
356 def test_package_to_other_package(self):
357 # Move a repository from one package to another.
358 repository = self.factory.makeGitRepository(
359 target=self.factory.makeDistributionSourcePackage())
360 dsp = self.factory.makeDistributionSourcePackage()
361 with person_logged_in(repository.owner):
362 repository.setTarget(target=dsp, user=repository.owner)
363 self.assertEqual(dsp, repository.target)
364
365 def test_package_to_project(self):
366 # Move a repository from a package to a project.
367 repository = self.factory.makeGitRepository(
368 target=self.factory.makeDistributionSourcePackage())
369 project = self.factory.makeProduct()
370 with person_logged_in(repository.owner):
371 repository.setTarget(target=project, user=repository.owner)
372 self.assertEqual(project, repository.target)
373
374 def test_package_to_personal(self):
375 # Move a repository from a package to a personal namespace.
376 owner = self.factory.makePerson()
377 repository = self.factory.makeGitRepository(
378 owner=owner, target=self.factory.makeDistributionSourcePackage())
379 with person_logged_in(owner):
380 repository.setTarget(target=owner, user=owner)
381 self.assertEqual(owner, repository.target)
382
383 def test_public_to_proprietary_only_project(self):
384 # A repository cannot be moved to a target where the sharing policy
385 # does not allow it.
386 owner = self.factory.makePerson()
387 commercial_project = self.factory.makeProduct(
388 owner=owner, branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
389 repository = self.factory.makeGitRepository(
390 owner=owner, information_type=InformationType.PUBLIC)
391 with admin_logged_in():
392 self.assertRaises(
393 GitTargetError, repository.setTarget,
394 target=commercial_project, user=owner)
0395
=== 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-20 00:57:45 +0000
@@ -2,7 +2,7 @@
2# NOTE: The first line above must stay first; do not move the copyright2# NOTE: The first line above must stay first; do not move the copyright
3# notice to the top. See http://www.python.org/dev/peps/pep-0263/.3# notice to the top. See http://www.python.org/dev/peps/pep-0263/.
4#4#
5# Copyright 2009-2014 Canonical Ltd. This software is licensed under the5# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
6# GNU Affero General Public License version 3 (see the file LICENSE).6# GNU Affero General Public License version 3 (see the file LICENSE).
77
8"""Testing infrastructure for the Launchpad application.8"""Testing infrastructure for the Launchpad application.
@@ -118,6 +118,7 @@
118from lp.code.interfaces.codeimportevent import ICodeImportEventSet118from lp.code.interfaces.codeimportevent import ICodeImportEventSet
119from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet119from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
120from lp.code.interfaces.codeimportresult import ICodeImportResultSet120from lp.code.interfaces.codeimportresult import ICodeImportResultSet
121from lp.code.interfaces.gitnamespace import get_git_namespace
121from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch122from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
122from lp.code.interfaces.revision import IRevisionSet123from lp.code.interfaces.revision import IRevisionSet
123from lp.code.interfaces.sourcepackagerecipe import (124from lp.code.interfaces.sourcepackagerecipe import (
@@ -1668,6 +1669,37 @@
1668 revision_date=revision_date)1669 revision_date=revision_date)
1669 return branch.createBranchRevision(sequence, revision)1670 return branch.createBranchRevision(sequence, revision)
16701671
1672 def makeGitRepository(self, owner=None, target=_DEFAULT, registrant=None,
1673 name=None, information_type=None,
1674 **optional_repository_args):
1675 """Create and return a new, arbitrary GitRepository.
1676
1677 Any parameters for `IGitNamespace.createRepository` can be specified
1678 to override the default ones.
1679 """
1680 if owner is None:
1681 owner = self.makePerson()
1682 if name is None:
1683 name = self.getUniqueString('gitrepository').decode('utf-8')
1684
1685 if target is _DEFAULT:
1686 target = self.makeProduct()
1687
1688 if registrant is None:
1689 if owner.is_team:
1690 registrant = removeSecurityProxy(owner).teamowner
1691 else:
1692 registrant = owner
1693
1694 namespace = get_git_namespace(target, owner)
1695 repository = namespace.createRepository(
1696 registrant=registrant, name=name, **optional_repository_args)
1697 naked_repository = removeSecurityProxy(repository)
1698 if information_type is not None:
1699 naked_repository.transitionToInformationType(
1700 information_type, registrant, verify_policy=False)
1701 return repository
1702
1671 def makeBug(self, target=None, owner=None, bug_watch_url=None,1703 def makeBug(self, target=None, owner=None, bug_watch_url=None,
1672 information_type=None, date_closed=None, title=None,1704 information_type=None, date_closed=None, title=None,
1673 date_created=None, description=None, comment=None,1705 date_created=None, description=None, comment=None,