Merge lp:~james-w/launchpad/archive-collection into lp:launchpad

Proposed by James Westby
Status: Work in progress
Proposed branch: lp:~james-w/launchpad/archive-collection
Merge into: lp:launchpad
Diff against target: 611 lines (+421/-70)
7 files modified
lib/lp/services/database/collection.py (+3/-3)
lib/lp/soyuz/configure.zcml (+13/-0)
lib/lp/soyuz/interfaces/archivecollection.py (+70/-0)
lib/lp/soyuz/model/archive.py (+17/-64)
lib/lp/soyuz/model/archivecollection.py (+86/-0)
lib/lp/soyuz/tests/test_archivecollection.py (+220/-0)
lib/lp/testing/factory.py (+12/-3)
To merge this branch: bzr merge lp:~james-w/launchpad/archive-collection
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+31499@code.launchpad.net

Description of the change

Hi,

Here's a branch to add an ArchiveCollection based
on the new generic Collection.

It's fairly standalone right now, though I ported a
few easy queries in ArchiveSet methods over to it.
Hopefully once it is available it will see more use.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (5.8 KiB)

On Mon, Aug 2, 2010 at 1:41 AM, James Westby <email address hidden> wrote:
> James Westby has proposed merging lp:~james-w/launchpad/archive-collection into lp:launchpad/devel.
>
> Requested reviews:
>  Launchpad code reviewers (launchpad-reviewers)
>
>
> Hi,
>
> Here's a branch to add an ArchiveCollection based
> on the new generic Collection.
>
> It's fairly standalone right now, though I ported a
> few easy queries in ArchiveSet methods over to it.
> Hopefully once it is available it will see more use.
>
>

Thanks for doing this. It's important to have new code actually be used.

> === modified file 'lib/lp/services/database/collection.py'
> --- lib/lp/services/database/collection.py      2010-07-16 14:10:18 +0000
> +++ lib/lp/services/database/collection.py      2010-08-02 00:41:06 +0000
> @@ -68,9 +68,6 @@
>             base_tables = list(base.tables)
>
>         self.store = kwargs.get('store')
> -        if self.store is None:
> -            self.store = getUtility(IStoreSelector).get(
> -                MAIN_STORE, DEFAULT_FLAVOR)
>
>         self.tables = (
>             starting_tables + base_tables +
> @@ -118,6 +115,9 @@
>         If no values are requested, this selects the type of object that
>         the Collection is a collection of.
>         """
> +        if self.store is None:
> +            self.store = getUtility(IStoreSelector).get(
> +                MAIN_STORE, DEFAULT_FLAVOR)
>         if len(self.tables) == 0:
>             source = self.store
>         else:
>

What's the reason for this change? Where are the corresponding tests?

> === added file 'lib/lp/soyuz/interfaces/archivecollection.py'
> --- lib/lp/soyuz/interfaces/archivecollection.py        1970-01-01 00:00:00 +0000
> +++ lib/lp/soyuz/interfaces/archivecollection.py        2010-08-02 00:41:06 +0000
> @@ -0,0 +1,70 @@
> +# Copyright 2010 Canonical Ltd.  This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""A collection of archives.
> +
...
> +
> +    def withPurposes(purposes):
> +        """Restrict the archives to one of the given purposes."""

In IBranchCollection, we have a *args method for something similar. Do
you think that would be nicer here?

> === added file 'lib/lp/soyuz/model/archivecollection.py'
> --- lib/lp/soyuz/model/archivecollection.py     1970-01-01 00:00:00 +0000
> +++ lib/lp/soyuz/model/archivecollection.py     2010-08-02 00:41:06 +0000
> @@ -0,0 +1,86 @@
> +# Copyright 2010 Canonical Ltd.  This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +__metaclass__ = type
> +
...
> +
> +    def visibleByUser(self, user):
> +        public_archive = And(Archive.private == False,
> +                             Archive._enabled == True)

Maybe it's my crappy email client, but the indentation on this looks messed up.

> +        if user is None:
> +            # Anonymous user; filter to include only public archives in
> +            # the results.
> +            return self.refine(public_archive)
> +        celebs = getUtility(ILaunchpadCelebrities)
> +        if user.inTeam(celebs.admin):
> +            # Admi...

Read more...

Revision history for this message
James Westby (james-w) wrote :

On Mon, 02 Aug 2010 15:58:40 -0000, Jonathan Lange <email address hidden> wrote:
> What's the reason for this change? Where are the corresponding tests?

Sorry, I forgot to mention it in the cover letter.

When you register a Collection as a utility it is instantiated by the
zcml.

However, as the store is queried in the constructor this can fail if
there is not a utility registered for the store yet.

How this manifested was that as soon as I added the utility I could no
longer run any tests as there was an exception during zcml processing.

I'm not sure how I would test this directly? A test that created a
Collection and tested that .store was None?

It does make me realise that I should probably add a test that the
utility returns something sensible?

> In IBranchCollection, we have a *args method for something similar. Do
> you think that would be nicer here?

The implementation is *purposes, so the interface needs correcting.

> Maybe it's my crappy email client, but the indentation on this looks messed up.

It's non-standard as it was copy-paste code, I will fix.

> > +        # FIXME: Include private PPA's if user is an uploader
> > +        return self.refine(
> > +            Or(public_archive, Archive.ownerID.is_in(user_teams_subselect)))
> >
>
> Are you going to fix this in this branch? Won't this be buggy if
> someone uses it?

This is copy-paste code, so we apparently have survived this far without
needing it.

I can look at fixing it though.

> These tests are good, thanks.
>
> One thing I've done with similar tests is have a setUp() that removes
> all of the data in question (e.g. removing all branches). That way you
> can do actual equality testing rather than testing to if if objects
> are members in tests.
>
> You don't have to though.

That's a good idea, I'll look for your code to learn from it.

Thanks,

James

Unmerged revisions

11270. By James Westby

Port a few more easy queries to ArchiveCollection.

11269. By James Westby

Move the first method over to the new code.

11268. By James Westby

Add the Interfaces and register a utility for all archives.

Registering a utility means that the zcml processor tries to instantiate
our class, which fails given that the IStoreSelector utility isn't
registered yet. Therefore we lazily get the store in the base Collection
if it isn't in the kwargs in the constructor.

11267. By James Westby

Implement visibleByUser.

11266. By James Westby

Basic ArchiveCollection implementation.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/services/database/collection.py'
2--- lib/lp/services/database/collection.py 2010-07-16 14:10:18 +0000
3+++ lib/lp/services/database/collection.py 2010-08-02 00:41:06 +0000
4@@ -68,9 +68,6 @@
5 base_tables = list(base.tables)
6
7 self.store = kwargs.get('store')
8- if self.store is None:
9- self.store = getUtility(IStoreSelector).get(
10- MAIN_STORE, DEFAULT_FLAVOR)
11
12 self.tables = (
13 starting_tables + base_tables +
14@@ -118,6 +115,9 @@
15 If no values are requested, this selects the type of object that
16 the Collection is a collection of.
17 """
18+ if self.store is None:
19+ self.store = getUtility(IStoreSelector).get(
20+ MAIN_STORE, DEFAULT_FLAVOR)
21 if len(self.tables) == 0:
22 source = self.store
23 else:
24
25=== modified file 'lib/lp/soyuz/configure.zcml'
26--- lib/lp/soyuz/configure.zcml 2010-06-30 17:35:36 +0000
27+++ lib/lp/soyuz/configure.zcml 2010-08-02 00:41:06 +0000
28@@ -67,6 +67,19 @@
29 interface="lp.soyuz.interfaces.archivearch.IArchiveArchSet"/>
30 </securedutility>
31
32+ <!-- ArchiveCollection -->
33+ <securedutility
34+ class="lp.soyuz.model.archivecollection.ArchiveCollection"
35+ provides="lp.soyuz.interfaces.archivecollection.IAllArchives">
36+ <allow
37+ interface="lp.soyuz.interfaces.archivecollection.IAllArchives" />
38+ </securedutility>
39+
40+ <class class="lp.soyuz.model.archivecollection.ArchiveCollection">
41+ <allow
42+ interface="lp.soyuz.interfaces.archivecollection.IAllArchives" />
43+ </class>
44+
45 <!-- DistroSeriesPackageCache -->
46 <class
47 class="canonical.launchpad.database.DistroSeriesPackageCache">
48
49=== added file 'lib/lp/soyuz/interfaces/archivecollection.py'
50--- lib/lp/soyuz/interfaces/archivecollection.py 1970-01-01 00:00:00 +0000
51+++ lib/lp/soyuz/interfaces/archivecollection.py 2010-08-02 00:41:06 +0000
52@@ -0,0 +1,70 @@
53+# Copyright 2010 Canonical Ltd. This software is licensed under the
54+# GNU Affero General Public License version 3 (see the file LICENSE).
55+
56+"""A collection of archives.
57+
58+See `IArchiveCollection` for more details.
59+"""
60+
61+__metaclass__ = type
62+__all__ = [
63+ 'IAllArchives',
64+ 'IArchiveCollection',
65+ ]
66+
67+from zope.interface import Interface
68+
69+
70+class IArchiveCollection(Interface):
71+ """A collection of archives.
72+
73+ An `IArchiveCollection` is an immutable collection of archives.
74+
75+ You can use the methods it provides to get a new collection with
76+ a filtered set of archives.
77+
78+ Finally you can call `select` to get a ResultSet from the collection.
79+ """
80+
81+ def select(*args):
82+ """Return a ResultSet for this collection, with values set to args."""
83+
84+ def withPurpose(purpose):
85+ """Restrict the archives to only the given purpose."""
86+
87+ def withPurposes(purposes):
88+ """Restrict the archives to one of the given purposes."""
89+
90+ def withStatus(status):
91+ """Restrict the archives to only the given status."""
92+
93+ def withDistribution(distribution):
94+ """Restrict the archives to only the given distribution."""
95+
96+ def ownedBy(owner):
97+ """Restrict the archives to only those owned by the given person."""
98+
99+ def withName(name):
100+ """Restrict the archives to only those with the given name."""
101+
102+ def isEnabled(enabled=True):
103+ """Restrict the archives to only those enabled as passed."""
104+
105+ def isPrivate(private=True):
106+ """Restrict the archives to only those with the given privacy."""
107+
108+ def isRequireVirtualized(require_virtualized=True):
109+ """Restrict the archives to those requiring virtualization."""
110+
111+ def isCommercial(commercial=True):
112+ """Restrict the archives to commercial as given."""
113+
114+ def visibleByUser(user):
115+ """Restrict the archives to those visible by the given user.
116+
117+ :param user: a Person, or None for public archives.
118+ """
119+
120+
121+class IAllArchives(IArchiveCollection):
122+ """An `IArchiveCollection` representing all archives in Launchpad."""
123
124=== modified file 'lib/lp/soyuz/model/archive.py'
125--- lib/lp/soyuz/model/archive.py 2010-07-27 12:53:24 +0000
126+++ lib/lp/soyuz/model/archive.py 2010-08-02 00:41:06 +0000
127@@ -40,6 +40,7 @@
128 from lp.soyuz.adapters.packagelocation import PackageLocation
129 from canonical.launchpad.components.tokens import (
130 create_unique_token_for_table)
131+from lp.soyuz.interfaces.archivecollection import IAllArchives
132 from lp.soyuz.model.archivedependency import ArchiveDependency
133 from lp.soyuz.model.archiveauthtoken import ArchiveAuthToken
134 from lp.soyuz.model.archivesubscriber import ArchiveSubscriber
135@@ -1810,13 +1811,10 @@
136
137 def getPPAOwnedByPerson(self, person, name=None):
138 """See `IArchiveSet`."""
139- store = Store.of(person)
140- clause = [
141- Archive.purpose == ArchivePurpose.PPA,
142- Archive.owner == person]
143+ ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA)
144 if name is not None:
145- clause.append(Archive.name == name)
146- result = store.find(Archive, *clause).order_by(Archive.id).first()
147+ ppas = ppas.withName(name)
148+ result = ppas.ownedBy(person).select().order_by(Archive.id).first()
149 if name is not None and result is None:
150 raise NoSuchPPA(name)
151 return result
152@@ -1954,79 +1952,34 @@
153
154 def getPrivatePPAs(self):
155 """See `IArchiveSet`."""
156- store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
157- return store.find(
158- Archive,
159- Archive.private == True,
160- Archive.purpose == ArchivePurpose.PPA)
161+ ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA)
162+ return ppas.isPrivate().select()
163
164 def getCommercialPPAs(self):
165 """See `IArchiveSet`."""
166- store = IStore(Archive)
167- return store.find(
168- Archive,
169- Archive.commercial == True,
170- Archive.purpose == ArchivePurpose.PPA)
171+ ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA)
172+ return ppas.isCommercial().select()
173
174 def getArchivesForDistribution(self, distribution, name=None,
175 purposes=None, user=None,
176 exclude_disabled=True):
177 """See `IArchiveSet`."""
178- extra_exprs = []
179+ archives = getUtility(IAllArchives).withDistribution(distribution)
180
181- # If a single purpose is passed in, convert it into a tuple,
182- # otherwise assume a list was passed in.
183 if purposes in ArchivePurpose:
184- purposes = (purposes,)
185-
186- if purposes:
187- extra_exprs.append(Archive.purpose.is_in(purposes))
188+ archives = archives.withPurpose(purposes)
189+ elif purposes:
190+ archives = archives.withPurposes(*purposes)
191
192 if name is not None:
193- extra_exprs.append(Archive.name == name)
194-
195- public_archive = And(Archive.private == False,
196- Archive._enabled == True)
197-
198- if user is not None:
199- admins = getUtility(ILaunchpadCelebrities).admin
200- if not user.inTeam(admins):
201- # Enforce privacy-awareness for logged-in, non-admin users,
202- # so that they can only see the private archives that they're
203- # allowed to see.
204-
205- # Create a subselect to capture all the teams that are
206- # owners of archives AND the user is a member of:
207- user_teams_subselect = Select(
208- TeamParticipation.teamID,
209- where=And(
210- TeamParticipation.personID == user.id,
211- TeamParticipation.teamID == Archive.ownerID))
212-
213- # Append the extra expression to capture either public
214- # archives, or archives owned by the user, or archives
215- # owned by a team of which the user is a member:
216- # Note: 'Archive.ownerID == user.id'
217- # is unnecessary below because there is a TeamParticipation
218- # entry showing that each person is a member of the "team"
219- # that consists of themselves.
220-
221- # FIXME: Include private PPA's if user is an uploader
222- extra_exprs.append(
223- Or(public_archive,
224- Archive.ownerID.is_in(user_teams_subselect)))
225- else:
226- # Anonymous user; filter to include only public archives in
227- # the results.
228- extra_exprs.append(public_archive)
229+ archives = archives.withName(name)
230+
231+ archives = archives.visibleByUser(user)
232
233 if exclude_disabled:
234- extra_exprs.append(Archive._enabled == True)
235+ archives = archives.isEnabled()
236
237- query = Store.of(distribution).find(
238- Archive,
239- Archive.distribution == distribution,
240- *extra_exprs)
241+ query = archives.select()
242
243 return query.order_by(Archive.name)
244
245
246=== added file 'lib/lp/soyuz/model/archivecollection.py'
247--- lib/lp/soyuz/model/archivecollection.py 1970-01-01 00:00:00 +0000
248+++ lib/lp/soyuz/model/archivecollection.py 2010-08-02 00:41:06 +0000
249@@ -0,0 +1,86 @@
250+# Copyright 2010 Canonical Ltd. This software is licensed under the
251+# GNU Affero General Public License version 3 (see the file LICENSE).
252+
253+__metaclass__ = type
254+
255+from storm.expr import And, Or, Select
256+from zope.component import getUtility
257+from zope.interface import implements
258+
259+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
260+from lp.registry.model.teammembership import TeamParticipation
261+from lp.services.database.collection import Collection
262+from lp.soyuz.interfaces.archivecollection import IArchiveCollection
263+from lp.soyuz.model.archive import Archive
264+
265+
266+class ArchiveCollection(Collection):
267+
268+ implements(IArchiveCollection)
269+
270+ starting_table = Archive
271+
272+ def withPurpose(self, purpose):
273+ return self.refine(Archive.purpose == purpose)
274+
275+ def withPurposes(self, *purposes):
276+ return self.refine(Archive.purpose.is_in(purposes))
277+
278+ def withStatus(self, status):
279+ return self.refine(Archive.status == status)
280+
281+ def withDistribution(self, distribution):
282+ return self.refine(Archive.distribution == distribution)
283+
284+ def ownedBy(self, owner):
285+ return self.refine(Archive.owner == owner)
286+
287+ def withName(self, name):
288+ return self.refine(Archive.name == name)
289+
290+ def isEnabled(self, enabled=True):
291+ return self.refine(Archive._enabled == enabled)
292+
293+ def isPrivate(self, private=True):
294+ return self.refine(Archive.private == private)
295+
296+ def isRequireVirtualized(self, require_virtualized=True):
297+ return self.refine(Archive.require_virtualized == require_virtualized)
298+
299+ def isCommercial(self, commercial=True):
300+ return self.refine(Archive.commercial == commercial)
301+
302+ def visibleByUser(self, user):
303+ public_archive = And(Archive.private == False,
304+ Archive._enabled == True)
305+ if user is None:
306+ # Anonymous user; filter to include only public archives in
307+ # the results.
308+ return self.refine(public_archive)
309+ celebs = getUtility(ILaunchpadCelebrities)
310+ if user.inTeam(celebs.admin):
311+ # Admins can see everything
312+ return self
313+ # Enforce privacy-awareness for non-admin users,
314+ # so that they can only see the private archives that they're
315+ # allowed to see.
316+
317+ # Create a subselect to capture all the teams that are
318+ # owners of archives AND the user is a member of:
319+ user_teams_subselect = Select(
320+ TeamParticipation.teamID,
321+ where=And(
322+ TeamParticipation.personID == user.id,
323+ TeamParticipation.teamID == Archive.ownerID))
324+
325+ # Append the extra expression to capture either public
326+ # archives, or archives owned by the user, or archives
327+ # owned by a team of which the user is a member:
328+ # Note: 'Archive.ownerID == user.id'
329+ # is unnecessary below because there is a TeamParticipation
330+ # entry showing that each person is a member of the "team"
331+ # that consists of themselves.
332+
333+ # FIXME: Include private PPA's if user is an uploader
334+ return self.refine(
335+ Or(public_archive, Archive.ownerID.is_in(user_teams_subselect)))
336
337=== added file 'lib/lp/soyuz/tests/test_archivecollection.py'
338--- lib/lp/soyuz/tests/test_archivecollection.py 1970-01-01 00:00:00 +0000
339+++ lib/lp/soyuz/tests/test_archivecollection.py 2010-08-02 00:41:06 +0000
340@@ -0,0 +1,220 @@
341+# Copyright 2010 Canonical Ltd. This software is licensed under the
342+# GNU Affero General Public License version 3 (see the file LICENSE).
343+
344+"""Tests for `ArchiveCollection`."""
345+
346+__metaclass__ = type
347+
348+from zope.component import getUtility
349+from zope.security.proxy import removeSecurityProxy
350+
351+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
352+from canonical.testing import DatabaseFunctionalLayer
353+from lp.soyuz.interfaces.archive import ArchivePurpose, ArchiveStatus
354+from lp.soyuz.model.archivecollection import ArchiveCollection
355+from lp.testing import TestCaseWithFactory
356+
357+
358+class TestGetPublicationsInArchive(TestCaseWithFactory):
359+
360+ layer = DatabaseFunctionalLayer
361+
362+ def test_baseline(self):
363+ archive = self.factory.makeArchive()
364+ collection = ArchiveCollection()
365+ self.assertIn(archive, collection.select())
366+
367+ def test_purpose_found(self):
368+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
369+ collection = ArchiveCollection()
370+ ppas = collection.withPurpose(ArchivePurpose.PPA)
371+ self.assertIn(archive, ppas.select())
372+
373+ def test_purpose_not_found(self):
374+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
375+ collection = ArchiveCollection()
376+ ppas = collection.withPurpose(ArchivePurpose.PPA)
377+ self.assertNotIn(archive, ppas.select())
378+
379+ def test_purposes(self):
380+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
381+ ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
382+ copy_archive = self.factory.makeArchive(purpose=ArchivePurpose.COPY)
383+ archives = ArchiveCollection().withPurposes(
384+ ArchivePurpose.PPA, ArchivePurpose.COPY)
385+ result = archives.select()
386+ self.assertIn(ppa, result)
387+ self.assertIn(copy_archive, result)
388+ self.assertNotIn(archive, result)
389+
390+ def test_distribution_found(self):
391+ distribution = self.factory.makeDistribution()
392+ archive = self.factory.makeArchive(distribution=distribution)
393+ archives = ArchiveCollection().withDistribution(distribution)
394+ self.assertIn(archive, archives.select())
395+
396+ def test_distribution_notfound(self):
397+ distribution = self.factory.makeDistribution()
398+ archive = self.factory.makeArchive(distribution=distribution)
399+ other_distribution = self.factory.makeDistribution()
400+ archives = ArchiveCollection().withDistribution(other_distribution)
401+ self.assertNotIn(archive, archives.select())
402+
403+ def test_owner_found(self):
404+ owner = self.factory.makePerson()
405+ archive = self.factory.makeArchive(owner=owner)
406+ archives = ArchiveCollection().ownedBy(owner)
407+ self.assertIn(archive, archives.select())
408+
409+ def test_owner_not_found(self):
410+ owner = self.factory.makePerson()
411+ archive = self.factory.makeArchive(owner=owner)
412+ other_person = self.factory.makePerson()
413+ archives = ArchiveCollection().ownedBy(other_person)
414+ self.assertNotIn(archive, archives.select())
415+
416+ def test_name_found(self):
417+ name = "foo"
418+ archive = self.factory.makeArchive(name=name)
419+ archives = ArchiveCollection().withName(name)
420+ self.assertIn(archive, archives.select())
421+
422+ def test_name_not_found(self):
423+ name = "foo"
424+ archive = self.factory.makeArchive(name=name)
425+ archives = ArchiveCollection().withName("bar")
426+ self.assertNotIn(archive, archives.select())
427+
428+ def test_enabled_found(self):
429+ archive = self.factory.makeArchive(enabled=True)
430+ archives = ArchiveCollection().isEnabled()
431+ self.assertIn(archive, archives.select())
432+
433+ def test_enabled_not_found(self):
434+ archive = self.factory.makeArchive(enabled=True)
435+ archives = ArchiveCollection().isEnabled(False)
436+ self.assertNotIn(archive, archives.select())
437+
438+ def test_disabled_found(self):
439+ archive = self.factory.makeArchive(enabled=False)
440+ archives = ArchiveCollection().isEnabled(False)
441+ self.assertIn(archive, archives.select())
442+
443+ def test_disabled_not_found(self):
444+ archive = self.factory.makeArchive(enabled=False)
445+ archives = ArchiveCollection().isEnabled()
446+ self.assertNotIn(archive, archives.select())
447+
448+ def test_private_found(self):
449+ archive = self.factory.makeArchive(private=True)
450+ archives = ArchiveCollection().isPrivate()
451+ self.assertIn(archive, archives.select())
452+
453+ def test_private_not_found(self):
454+ archive = self.factory.makeArchive(private=True)
455+ archives = ArchiveCollection().isPrivate(False)
456+ self.assertNotIn(archive, archives.select())
457+
458+ def test_non_private_found(self):
459+ archive = self.factory.makeArchive(private=False)
460+ archives = ArchiveCollection().isPrivate(False)
461+ self.assertIn(archive, archives.select())
462+
463+ def test_non_private_not_found(self):
464+ archive = self.factory.makeArchive(private=False)
465+ archives = ArchiveCollection().isPrivate()
466+ self.assertNotIn(archive, archives.select())
467+
468+ def test_require_virtualized_found(self):
469+ archive = self.factory.makeArchive(virtualized=True)
470+ archives = ArchiveCollection().isRequireVirtualized()
471+ self.assertIn(archive, archives.select())
472+
473+ def test_require_virtualized_not_found(self):
474+ archive = self.factory.makeArchive(virtualized=True)
475+ archives = ArchiveCollection().isRequireVirtualized(False)
476+ self.assertNotIn(archive, archives.select())
477+
478+ def test_not_require_virtualized_found(self):
479+ archive = self.factory.makeArchive(virtualized=False)
480+ archives = ArchiveCollection().isRequireVirtualized(False)
481+ self.assertIn(archive, archives.select())
482+
483+ def test_not_require_virtualized_not_found(self):
484+ archive = self.factory.makeArchive(virtualized=False)
485+ archives = ArchiveCollection().isRequireVirtualized()
486+ self.assertNotIn(archive, archives.select())
487+
488+ def test_status_found(self):
489+ archive = self.factory.makeArchive(status=ArchiveStatus.ACTIVE)
490+ archives = ArchiveCollection().withStatus(ArchiveStatus.ACTIVE)
491+ self.assertIn(archive, archives.select())
492+
493+ def test_status_not_found(self):
494+ archive = self.factory.makeArchive(status=ArchiveStatus.ACTIVE)
495+ archives = ArchiveCollection().withStatus(ArchiveStatus.DELETING)
496+ self.assertNotIn(archive, archives.select())
497+
498+ def test_commercial_found(self):
499+ archive = self.factory.makeArchive(commercial=True)
500+ archives = ArchiveCollection().isCommercial()
501+ self.assertIn(archive, archives.select())
502+
503+ def test_commercial_not_found(self):
504+ archive = self.factory.makeArchive(commercial=True)
505+ archives = ArchiveCollection().isCommercial(False)
506+ self.assertNotIn(archive, archives.select())
507+
508+ def test_not_commercial_not_found(self):
509+ archive = self.factory.makeArchive(commercial=False)
510+ archives = ArchiveCollection().isCommercial()
511+ self.assertNotIn(archive, archives.select())
512+
513+ def test_not_commercial_found(self):
514+ archive = self.factory.makeArchive(commercial=False)
515+ archives = ArchiveCollection().isCommercial(False)
516+ self.assertIn(archive, archives.select())
517+
518+ def test_admins_can_see_all(self):
519+ archive = self.factory.makeArchive(private=True)
520+ all_archives = ArchiveCollection()
521+ admin = self.factory.makePerson()
522+ admin_team = removeSecurityProxy(
523+ getUtility(ILaunchpadCelebrities).admin)
524+ admin_team.addMember(admin, admin_team.teamowner)
525+ archives = ArchiveCollection().visibleByUser(admin)
526+ self.assertEqual(
527+ sorted(all_archives.select()), sorted(archives.select()))
528+
529+ def test_plain_user_cant_see_private(self):
530+ archive = self.factory.makeArchive(private=True)
531+ person = self.factory.makePerson()
532+ archives = ArchiveCollection().visibleByUser(person)
533+ self.assertNotIn(archive, archives.select())
534+
535+ def test_plain_user_cant_see_disabled(self):
536+ archive = self.factory.makeArchive(enabled=False, private=False)
537+ person = self.factory.makePerson()
538+ archives = ArchiveCollection().visibleByUser(person)
539+ self.assertNotIn(archive, archives.select())
540+
541+ def test_plain_user_can_see_public_enabled(self):
542+ archive = self.factory.makeArchive(enabled=True, private=False)
543+ person = self.factory.makePerson()
544+ archives = ArchiveCollection().visibleByUser(person)
545+ self.assertIn(archive, archives.select())
546+
547+ def test_user_can_see_private_disabled_archives_they_own(self):
548+ person = self.factory.makePerson()
549+ archive = self.factory.makeArchive(
550+ enabled=False, private=True, owner=person)
551+ archives = ArchiveCollection().visibleByUser(person)
552+ self.assertIn(archive, archives.select())
553+
554+ def test_user_can_see_private_disabled_archives_for_their_teams(self):
555+ person = self.factory.makePerson()
556+ team = self.factory.makeTeam(members=[person])
557+ archive = self.factory.makeArchive(
558+ enabled=False, private=True, owner=team)
559+ archives = ArchiveCollection().visibleByUser(person)
560+ self.assertIn(archive, archives.select())
561
562=== modified file 'lib/lp/testing/factory.py'
563--- lib/lp/testing/factory.py 2010-08-01 04:34:26 +0000
564+++ lib/lp/testing/factory.py 2010-08-02 00:41:06 +0000
565@@ -144,7 +144,7 @@
566
567 from lp.soyuz.adapters.packagelocation import PackageLocation
568 from lp.soyuz.interfaces.archive import (
569- default_name_by_purpose, IArchiveSet, ArchivePurpose)
570+ default_name_by_purpose, IArchiveSet, ArchivePurpose, ArchiveStatus)
571 from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
572 from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
573 from lp.soyuz.interfaces.component import IComponentSet
574@@ -1731,7 +1731,8 @@
575
576 def makeArchive(self, distribution=None, owner=None, name=None,
577 purpose=None, enabled=True, private=False,
578- virtualized=True, description=None, displayname=None):
579+ virtualized=True, description=None, displayname=None,
580+ commercial=False, status=None):
581 """Create and return a new arbitrary archive.
582
583 :param distribution: Supply IDistribution, defaults to a new one
584@@ -1744,6 +1745,8 @@
585 :param private: Whether the archive is created private.
586 :param virtualized: Whether the archive is virtualized.
587 :param description: A description of the archive.
588+ :param commercial: Whether the archive is commercial.
589+ :param status: The ArchiveStatus of the archive.
590 """
591 if purpose is None:
592 purpose = ArchivePurpose.PPA
593@@ -1772,11 +1775,17 @@
594 enabled=enabled, require_virtualized=virtualized,
595 description=description)
596
597- if private:
598+ if private or commercial:
599 naked_archive = removeSecurityProxy(archive)
600 naked_archive.private = True
601 naked_archive.buildd_secret = "sekrit"
602
603+ if commercial:
604+ removeSecurityProxy(archive).commercial = True
605+
606+ if status is not None:
607+ removeSecurityProxy(archive).status = status
608+
609 return archive
610
611 def makeBuilder(self, processor=None, url=None, name=None, title=None,