Merge ~ruinedyourlife/launchpad:feat-archive-metadata-overrides into launchpad:master

Proposed by Quentin Debhi
Status: Merged
Approved by: Quentin Debhi
Approved revision: b8d89e18bdb39d34963ee0d1817ae237a4c3a07c
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ruinedyourlife/launchpad:feat-archive-metadata-overrides
Merge into: launchpad:master
Diff against target: 819 lines (+648/-1)
6 files modified
lib/lp/soyuz/browser/tests/test_archive_webservice.py (+308/-0)
lib/lp/soyuz/interfaces/archive.py (+35/-0)
lib/lp/soyuz/model/archive.py (+35/-0)
lib/lp/soyuz/stories/webservice/xx-archive.rst (+1/-0)
lib/lp/soyuz/tests/test_archive.py (+267/-1)
lib/lp/testing/factory.py (+2/-0)
Reviewer Review Type Date Requested Status
Guruprasad Approve
Review via email: mp+463030@code.launchpad.net

Commit message

Add new field Archive.metadata_overrides to models and interfaces

This field can be used for storing per-archive metadata overrides.
Also expose this field via the API and allow authorized users to
view and set its values.

To post a comment you must log in.
Revision history for this message
Ines Almeida (ines-almeida) wrote :

LGTM! Just a very small suggestion.

I was wondering if there are any tests to update or add, but maybe not since this new model attribute isn't really used yet - we'll do it when adding APIs/UI.

Note that this cannot be merged until the DB change is deployed to production. I'm happy to approve this one, but I won't for now because of that :)

Revision history for this message
Quentin Debhi (ruinedyourlife) wrote :

I glanced over the tests, and I didn't see anything that called for an update, besides the factory method itself to populate the `metadata_overrides` field.

Adding though, I wouldn't know at all.

Thanks for the insight!

Revision history for this message
Guruprasad (lgp171188) wrote :

This is a great start. I have left some review comments to address below.

review: Needs Fixing
Revision history for this message
Guruprasad (lgp171188) :
Revision history for this message
Guruprasad (lgp171188) wrote :

Like we discussed in the code review face-to-face meeting, a non-subscriber, non-owner, non-admin user should not get any information about the existence of a private archive, but it looks like that is happening in a test.

Can you create an empty archive in production, request Clint to make it private and share the URL with me so that we can test and verify the expected behaviour?

The changes look good to me and I will take a quick look and approve after you have addressed all the comments.

Revision history for this message
Guruprasad (lgp171188) wrote (last edit ):

> Like we discussed in the code review face-to-face meeting, a non-subscriber,
> non-owner, non-admin user should not get any information about the existence
> of a private archive, but it looks like that is happening in a test.
>
> Can you create an empty archive in production, request Clint to make it
> private and share the URL with me so that we can test and verify the expected
> behaviour?

We tested this in production by creating a PPA and trying to access it via the API as an anonymous user and a logged-in non-subscriber user and I can confirm the API does return non-private details about the archive. Relevant Mattermost chat conversation logs - https://chat.canonical.com/canonical/pl/kmyr4nsi8bnqbbqnka4rjb883h.

Revision history for this message
Quentin Debhi (ruinedyourlife) :
Revision history for this message
Quentin Debhi (ruinedyourlife) :
Revision history for this message
Guruprasad (lgp171188) wrote :

LGTM 👍 Please address the open review comments before squashing the commits into one, force pushing to this MP's source branch, and then set the status of this MP to 'Approved'.

review: Approve
Revision history for this message
Quentin Debhi (ruinedyourlife) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/soyuz/browser/tests/test_archive_webservice.py b/lib/lp/soyuz/browser/tests/test_archive_webservice.py
2index 598a015..57130d6 100644
3--- a/lib/lp/soyuz/browser/tests/test_archive_webservice.py
4+++ b/lib/lp/soyuz/browser/tests/test_archive_webservice.py
5@@ -35,6 +35,7 @@ from lp.testing import (
6 TestCaseWithFactory,
7 admin_logged_in,
8 api_url,
9+ celebrity_logged_in,
10 login,
11 person_logged_in,
12 record_two_runs,
13@@ -1266,3 +1267,310 @@ class TestArchiveSet(TestCaseWithFactory):
14 .jsonBody()
15 )
16 self.assertEqual(body["reference"], reference)
17+
18+
19+class TestArchiveMetadataOverridesWebService(TestCaseWithFactory):
20+ layer = DatabaseFunctionalLayer
21+
22+ def setUp(self):
23+ super().setUp()
24+ self.default_overrides = {
25+ "Origin": "default_origin",
26+ "Label": "default_label",
27+ "Suite": "default_suite",
28+ "Snapshots": "default_snapshots",
29+ }
30+
31+ def create_archive(self, owner=None, private=False, primary=False):
32+ if primary:
33+ distribution = self.factory.makeDistribution(owner=owner)
34+ archive = self.factory.makeArchive(
35+ owner=owner,
36+ distribution=distribution,
37+ purpose=ArchivePurpose.PRIMARY,
38+ )
39+ with celebrity_logged_in("admin"):
40+ archive.setMetadataOverrides(self.default_overrides)
41+ return archive
42+
43+ return self.factory.makeArchive(
44+ owner=owner,
45+ private=private,
46+ metadata_overrides=self.default_overrides,
47+ )
48+
49+ def get_and_check_response(self, person, archive, expected_body=None):
50+ archive_url = api_url(archive)
51+ webservice = webservice_for_person(
52+ person,
53+ permission=OAuthPermission.WRITE_PRIVATE,
54+ default_api_version="devel",
55+ )
56+ response = webservice.get(archive_url)
57+ self.assertEqual(200, response.status)
58+ if expected_body:
59+ self.assertEqual(
60+ expected_body,
61+ response.jsonBody()["metadata_overrides"],
62+ )
63+
64+ def patch_and_check_response(
65+ self, person, archive, overrides, expected_status, expected_body=None
66+ ):
67+ archive_url = api_url(archive)
68+ webservice = webservice_for_person(
69+ person,
70+ permission=OAuthPermission.WRITE_PRIVATE,
71+ default_api_version="devel",
72+ )
73+ response = webservice.patch(
74+ archive_url,
75+ "application/json",
76+ json.dumps({"metadata_overrides": overrides}),
77+ )
78+ self.assertEqual(expected_status, response.status)
79+ if expected_body:
80+ with person_logged_in(person):
81+ self.assertEqual(expected_body, archive.metadata_overrides)
82+
83+ def patch_and_check_structure(
84+ self, webservice, achive_url, data, expected_status, expected_body
85+ ):
86+ response = webservice.patch(
87+ achive_url, "application/json", json.dumps(data)
88+ )
89+ self.assertThat(
90+ response,
91+ MatchesStructure.byEquality(
92+ status=expected_status,
93+ body=expected_body,
94+ ),
95+ )
96+
97+ def test_cannot_set_invalid_metadata_keys(self):
98+ owner = self.factory.makePerson()
99+ archive = self.create_archive(owner=owner)
100+ archive_url = api_url(archive)
101+ webservice = webservice_for_person(
102+ owner,
103+ permission=OAuthPermission.WRITE_PRIVATE,
104+ default_api_version="devel",
105+ )
106+ invalid_overrides = {"metadata_overrides": {"Invalid": "test_invalid"}}
107+ self.patch_and_check_structure(
108+ webservice,
109+ archive_url,
110+ invalid_overrides,
111+ 400,
112+ (
113+ b"Invalid metadata override key. Allowed keys are "
114+ b"{'Label', 'Origin', 'Snapshots', 'Suite'}."
115+ ),
116+ )
117+
118+ def test_cannot_set_non_string_values_for_metadata(self):
119+ owner = self.factory.makePerson()
120+ archive = self.create_archive(owner=owner)
121+ archive_url = api_url(archive)
122+ webservice = webservice_for_person(
123+ owner,
124+ permission=OAuthPermission.WRITE_PRIVATE,
125+ default_api_version="devel",
126+ )
127+ invalid_values = ["", None, True, 1, [], {}]
128+ for value in invalid_values:
129+ data = {"metadata_overrides": {"Origin": value}}
130+ self.patch_and_check_structure(
131+ webservice,
132+ archive_url,
133+ data,
134+ 400,
135+ b"Value for 'Origin' must be a non-empty string.",
136+ )
137+
138+ def test_non_owner_can_view_public_archive_metadata_overrides(self):
139+ user = self.factory.makePerson()
140+ archive = self.create_archive()
141+ self.get_and_check_response(user, archive, self.default_overrides)
142+
143+ def test_owner_can_view_own_public_archive_metadata_overrides(self):
144+ owner = self.factory.makePerson()
145+ archive = self.create_archive(owner=owner)
146+ self.get_and_check_response(owner, archive, self.default_overrides)
147+
148+ def test_admin_can_view_metadata_overrides_of_any_public_archive(self):
149+ admin = self.factory.makeAdministrator()
150+ archive = self.create_archive()
151+ self.get_and_check_response(admin, archive, self.default_overrides)
152+
153+ def test_owner_can_view_own_private_archive_metadata_overrides(self):
154+ owner = self.factory.makePerson()
155+ private_archive = self.create_archive(owner=owner, private=True)
156+ self.get_and_check_response(
157+ owner, private_archive, self.default_overrides
158+ )
159+
160+ def test_admin_can_view_metadata_overrides_of_any_private_archive(self):
161+ admin = self.factory.makeAdministrator()
162+ private_archive = self.create_archive(private=True)
163+ self.get_and_check_response(
164+ admin, private_archive, self.default_overrides
165+ )
166+
167+ def test_non_owner_cannot_view_private_archive_metadata_overrides(self):
168+ owner = self.factory.makePerson()
169+ user = self.factory.makePerson()
170+ private_archive = self.create_archive(owner=owner, private=True)
171+ self.get_and_check_response(
172+ user,
173+ private_archive,
174+ "tag:launchpad.net:2008:redacted",
175+ )
176+
177+ def test_subscriber_cannot_view_private_archive_metadata_overrides(self):
178+ owner = self.factory.makePerson()
179+ user = self.factory.makePerson()
180+ private_archive = self.create_archive(owner=owner, private=True)
181+ with person_logged_in(owner):
182+ private_archive.newSubscription(user, owner)
183+ self.get_and_check_response(
184+ user,
185+ private_archive,
186+ "tag:launchpad.net:2008:redacted",
187+ )
188+
189+ def test_owner_can_set_metadata_overrides_on_own_public_archive(self):
190+ owner = self.factory.makePerson()
191+ archive = self.create_archive(owner=owner)
192+ with person_logged_in(owner):
193+ self.assertEqual(
194+ archive.metadata_overrides, self.default_overrides
195+ )
196+ overrides = {"Origin": "test_origin"}
197+ self.patch_and_check_response(
198+ owner, archive, overrides, 209, overrides
199+ )
200+
201+ def test_admin_can_set_metadata_overrides_on_any_public_archive(self):
202+ admin = self.factory.makeAdministrator()
203+ archive = self.create_archive()
204+ with celebrity_logged_in("admin"):
205+ self.assertEqual(
206+ archive.metadata_overrides, self.default_overrides
207+ )
208+ overrides = {"Origin": "test_origin"}
209+ self.patch_and_check_response(
210+ admin, archive, overrides, 209, overrides
211+ )
212+
213+ def test_non_owner_cannot_set_metadata_overrides_on_public_archive(self):
214+ owner = self.factory.makePerson()
215+ user = self.factory.makePerson()
216+ archive = self.create_archive(owner=owner)
217+ archive_url = api_url(archive)
218+ webservice = webservice_for_person(
219+ user,
220+ permission=OAuthPermission.WRITE_PRIVATE,
221+ default_api_version="devel",
222+ )
223+ overrides = {"metadata_overrides": {"Origin": "test_origin"}}
224+ self.patch_and_check_structure(
225+ webservice,
226+ archive_url,
227+ overrides,
228+ 401,
229+ b"(<Archive object>, 'setMetadataOverrides', 'launchpad.Edit')",
230+ )
231+
232+ def test_owner_can_set_metadata_overrides_on_private_archive(self):
233+ owner = self.factory.makePerson()
234+ private_archive = self.create_archive(owner=owner, private=True)
235+ with person_logged_in(owner):
236+ self.assertEqual(
237+ private_archive.metadata_overrides, self.default_overrides
238+ )
239+ overrides = {"Origin": "test_origin"}
240+ self.patch_and_check_response(
241+ owner, private_archive, overrides, 209, overrides
242+ )
243+
244+ def test_admin_can_set_metadata_overrides_on_any_private_archive(self):
245+ admin = self.factory.makeAdministrator()
246+ private_archive = self.create_archive(private=True)
247+ with celebrity_logged_in("admin"):
248+ self.assertEqual(
249+ private_archive.metadata_overrides, self.default_overrides
250+ )
251+ overrides = {"Origin": "test_origin"}
252+ self.patch_and_check_response(
253+ admin, private_archive, overrides, 209, overrides
254+ )
255+
256+ def test_non_owner_cannot_set_metadata_overrides_on_private_archive(self):
257+ user = self.factory.makePerson()
258+ private_archive = self.create_archive(primary=True)
259+ archive_url = api_url(private_archive)
260+ webservice = webservice_for_person(
261+ user,
262+ permission=OAuthPermission.WRITE_PRIVATE,
263+ default_api_version="devel",
264+ )
265+ overrides = {"metadata_overrides": {"Origin": "test_origin"}}
266+ self.patch_and_check_structure(
267+ webservice,
268+ archive_url,
269+ overrides,
270+ 401,
271+ b"(<Archive object>, 'setMetadataOverrides', 'launchpad.Edit')",
272+ )
273+
274+ def test_subscriber_cannot_set_metadata_overrides_on_private_archive(self):
275+ owner = self.factory.makePerson()
276+ user = self.factory.makePerson()
277+ private_archive = self.create_archive(owner=owner, private=True)
278+ with person_logged_in(owner):
279+ private_archive.newSubscription(user, owner)
280+ overrides = {"Origin": "test_origin"}
281+ self.patch_and_check_response(user, private_archive, overrides, 401)
282+
283+ def test_owner_can_set_metadata_overrides_on_own_primary_archive(self):
284+ owner = self.factory.makePerson()
285+ primary_archive = self.create_archive(owner=owner, primary=True)
286+ with person_logged_in(owner):
287+ self.assertEqual(
288+ primary_archive.metadata_overrides, self.default_overrides
289+ )
290+ overrides = {"Origin": "test_origin"}
291+ self.patch_and_check_response(
292+ owner, primary_archive, overrides, 209, overrides
293+ )
294+
295+ def test_admin_can_set_metadata_overrides_on_any_primary_archive(self):
296+ admin = self.factory.makeAdministrator()
297+ primary_archive = self.create_archive(primary=True)
298+ with celebrity_logged_in("admin"):
299+ self.assertEqual(
300+ primary_archive.metadata_overrides, self.default_overrides
301+ )
302+ overrides = {"Origin": "test_origin"}
303+ self.patch_and_check_response(
304+ admin, primary_archive, overrides, 209, overrides
305+ )
306+
307+ def test_non_owner_cannot_set_metadata_overrides_on_primary_archive(self):
308+ user = self.factory.makePerson()
309+ primary_archive = self.create_archive(primary=True)
310+ archive_url = api_url(primary_archive)
311+ webservice = webservice_for_person(
312+ user,
313+ permission=OAuthPermission.WRITE_PRIVATE,
314+ default_api_version="devel",
315+ )
316+ overrides = {"metadata_overrides": {"Origin": "test_origin"}}
317+ self.patch_and_check_structure(
318+ webservice,
319+ archive_url,
320+ overrides,
321+ 401,
322+ b"(<Archive object>, 'setMetadataOverrides', 'launchpad.Edit')",
323+ )
324diff --git a/lib/lp/soyuz/interfaces/archive.py b/lib/lp/soyuz/interfaces/archive.py
325index 780895a..f580599 100644
326--- a/lib/lp/soyuz/interfaces/archive.py
327+++ b/lib/lp/soyuz/interfaces/archive.py
328@@ -72,6 +72,7 @@ from lazr.restful.declarations import (
329 exported,
330 exported_as_webservice_collection,
331 exported_as_webservice_entry,
332+ mutator_for,
333 operation_for_version,
334 operation_parameters,
335 operation_returns_collection_of,
336@@ -79,11 +80,13 @@ from lazr.restful.declarations import (
337 rename_parameters_as,
338 )
339 from lazr.restful.fields import CollectionField, Reference
340+from lazr.restful.interface import copy_field
341 from zope.interface import Attribute, Interface
342 from zope.schema import (
343 Bool,
344 Choice,
345 Datetime,
346+ Dict,
347 Int,
348 List,
349 Object,
350@@ -1021,6 +1024,24 @@ class IArchiveView(IHasBuildRecords):
351 "pending publications."
352 )
353
354+ metadata_overrides = exported(
355+ Dict(
356+ title=_(
357+ "A JSON object containing metadata overrides for this archive."
358+ ),
359+ description=_(
360+ "Accepted keys are ... The values for all these keys should "
361+ "be a string and can use a '{series}' placeholder that will "
362+ "get substituted the name of the series that is currently "
363+ "being published."
364+ ),
365+ key_type=TextLine(),
366+ required=False,
367+ readonly=True,
368+ ),
369+ as_of="devel",
370+ )
371+
372 processors = exported(
373 CollectionField(
374 title=_("Processors"),
375@@ -2664,6 +2685,19 @@ class IArchiveEdit(Interface):
376 :param names: A list of token names.
377 """
378
379+ @mutator_for(IArchiveView["metadata_overrides"])
380+ @operation_parameters(
381+ metadata_overrides=copy_field(IArchiveView["metadata_overrides"]),
382+ )
383+ @export_write_operation()
384+ @operation_for_version("devel")
385+ def setMetadataOverrides(metadata_overrides):
386+ """Set the metadata overrides for this archive.
387+
388+ :param metadata_overrides: A JSON object containing metadata overrides
389+ for this archive.
390+ """
391+
392
393 class IArchiveDelete(Interface):
394 """Archive interface for operations restricted by delete privilege."""
395@@ -2772,6 +2806,7 @@ class IArchiveSet(Interface):
396 processors=None,
397 publishing_method=None,
398 repository_format=None,
399+ metadata_overrides=None,
400 ):
401 """Create a new archive.
402
403diff --git a/lib/lp/soyuz/model/archive.py b/lib/lp/soyuz/model/archive.py
404index f57a931..3e23a51 100644
405--- a/lib/lp/soyuz/model/archive.py
406+++ b/lib/lp/soyuz/model/archive.py
407@@ -11,6 +11,7 @@ __all__ = [
408 "validate_ppa",
409 ]
410
411+import http.client
412 import logging
413 import re
414 import typing
415@@ -20,6 +21,8 @@ from pathlib import PurePath
416
417 import six
418 from lazr.lifecycle.event import ObjectCreatedEvent
419+from lazr.restful.declarations import error_status
420+from storm.databases.postgres import JSON as PgJSON
421 from storm.expr import (
422 Alias,
423 And,
424@@ -196,6 +199,13 @@ ARCHIVE_REFERENCE_TEMPLATES = {
425 }
426
427
428+@error_status(http.client.BAD_REQUEST)
429+class CannotSetMetadataOverrides(Exception):
430+ """Raised when someone tries to set invalid metadata overrides keys or
431+ non empty string values.
432+ """
433+
434+
435 def storm_validate_external_dependencies(archive, attr, value):
436 assert attr == "external_dependencies"
437 errors = validate_external_dependencies(value)
438@@ -366,6 +376,8 @@ class Archive(StormBase):
439
440 dirty_suites = JSON(name="dirty_suites", allow_none=True)
441
442+ metadata_overrides = PgJSON(name="metadata_overrides", allow_none=True)
443+
444 _publishing_method = DBEnum(
445 name="publishing_method", allow_none=True, enum=ArchivePublishingMethod
446 )
447@@ -390,6 +402,7 @@ class Archive(StormBase):
448 signing_key_fingerprint=None,
449 publishing_method=None,
450 repository_format=None,
451+ metadata_overrides=None,
452 ):
453 super().__init__()
454 try:
455@@ -406,6 +419,7 @@ class Archive(StormBase):
456 self.signing_key_fingerprint = signing_key_fingerprint
457 self.publishing_method = publishing_method
458 self.repository_format = repository_format
459+ self.metadata_overrides = metadata_overrides
460 except Exception:
461 # If validating references such as `owner` fails, then the new
462 # object may have been added to the store first. Remove it
463@@ -2733,6 +2747,25 @@ class Archive(StormBase):
464 token_set = getUtility(IArchiveAuthTokenSet)
465 token_set.deactivateNamedTokensForArchive(self, names)
466
467+ def setMetadataOverrides(self, metadata_overrides):
468+ """See `IArchive`."""
469+ allowed_keys = sorted(["Origin", "Label", "Suite", "Snapshots"])
470+
471+ if not all(key in allowed_keys for key in metadata_overrides.keys()):
472+ allowed_keys_str = ", ".join(f"'{k}'" for k in allowed_keys)
473+ raise CannotSetMetadataOverrides(
474+ "Invalid metadata override key. Allowed keys are "
475+ f"{{{allowed_keys_str}}}."
476+ )
477+
478+ for key, value in metadata_overrides.items():
479+ if not isinstance(value, str) or not value:
480+ raise CannotSetMetadataOverrides(
481+ f"Value for '{key}' must be a non-empty string."
482+ )
483+
484+ self.metadata_overrides = metadata_overrides
485+
486 def newSubscription(
487 self, subscriber, registrant, date_expires=None, description=None
488 ):
489@@ -3430,6 +3463,7 @@ class ArchiveSet:
490 processors=None,
491 publishing_method=ArchivePublishingMethod.LOCAL,
492 repository_format=ArchiveRepositoryFormat.DEBIAN,
493+ metadata_overrides=None,
494 ):
495 """See `IArchiveSet`."""
496 if distribution is None:
497@@ -3516,6 +3550,7 @@ class ArchiveSet:
498 require_virtualized=require_virtualized,
499 publishing_method=publishing_method,
500 repository_format=repository_format,
501+ metadata_overrides=metadata_overrides,
502 )
503
504 # Upon creation archives are enabled by default.
505diff --git a/lib/lp/soyuz/stories/webservice/xx-archive.rst b/lib/lp/soyuz/stories/webservice/xx-archive.rst
506index b308252..931c4d2 100644
507--- a/lib/lp/soyuz/stories/webservice/xx-archive.rst
508+++ b/lib/lp/soyuz/stories/webservice/xx-archive.rst
509@@ -56,6 +56,7 @@ For "devel" additional attributes are available.
510 enabled_restricted_processors_collection_link:
511 'http://.../~cprov/+archive/ubuntu/ppa/enabled_restricted_processors'
512 external_dependencies: None
513+ metadata_overrides: None
514 name: 'ppa'
515 owner_link: 'http://.../~cprov'
516 permit_obsolete_series_uploads: False
517diff --git a/lib/lp/soyuz/tests/test_archive.py b/lib/lp/soyuz/tests/test_archive.py
518index 42a8fba..01e0708 100644
519--- a/lib/lp/soyuz/tests/test_archive.py
520+++ b/lib/lp/soyuz/tests/test_archive.py
521@@ -113,7 +113,11 @@ from lp.soyuz.interfaces.publishing import (
522 active_publishing_status,
523 inactive_publishing_status,
524 )
525-from lp.soyuz.model.archive import Archive, validate_ppa
526+from lp.soyuz.model.archive import (
527+ Archive,
528+ CannotSetMetadataOverrides,
529+ validate_ppa,
530+)
531 from lp.soyuz.model.archivepermission import (
532 ArchivePermission,
533 ArchivePermissionSet,
534@@ -6975,3 +6979,265 @@ class TestArchivePermissions(TestCaseWithFactory):
535 archive = self.factory.makeArchive(owner=owner)
536 login_person(archive.owner)
537 self.assertTrue(check_permission("launchpad.Admin", archive))
538+
539+
540+class TestArchiveMetadataOverrides(TestCaseWithFactory):
541+ layer = DatabaseFunctionalLayer
542+
543+ def setUp(self):
544+ super().setUp()
545+ self.default_metadata_overrides = {
546+ "Origin": "default_origin",
547+ "Label": "default_label",
548+ "Suite": "default_suite",
549+ "Snapshots": "default_snapshots",
550+ }
551+
552+ def create_archive(self, owner=None, private=False, primary=False):
553+ if primary:
554+ distribution = self.factory.makeDistribution(owner=owner)
555+ archive = self.factory.makeArchive(
556+ owner=owner,
557+ distribution=distribution,
558+ purpose=ArchivePurpose.PRIMARY,
559+ )
560+ with celebrity_logged_in("admin"):
561+ archive.setMetadataOverrides(self.default_metadata_overrides)
562+ return archive
563+
564+ return self.factory.makeArchive(
565+ owner=owner,
566+ private=private,
567+ metadata_overrides=self.default_metadata_overrides,
568+ )
569+
570+ def test_cannot_set_invalid_metadata_keys(self):
571+ owner = self.factory.makePerson()
572+ archive = self.create_archive(owner=owner)
573+ login_person(owner)
574+ self.assertRaises(
575+ CannotSetMetadataOverrides,
576+ archive.setMetadataOverrides,
577+ {"Invalid": "test_invalid"},
578+ )
579+
580+ def test_cannot_set_non_string_values_for_metadata(self):
581+ owner = self.factory.makePerson()
582+ archive = self.create_archive(owner=owner)
583+ login_person(owner)
584+ invalid_values = ["", None, True, 1, [], {}]
585+ for value in invalid_values:
586+ self.assertRaises(
587+ CannotSetMetadataOverrides,
588+ archive.setMetadataOverrides,
589+ {"Origin": value},
590+ )
591+
592+ def test_anonymous_can_view_public_archive_metadata_overrides(self):
593+ archive = self.create_archive()
594+ login(ANONYMOUS)
595+ self.assertEqual(
596+ archive.metadata_overrides,
597+ self.default_metadata_overrides,
598+ )
599+
600+ def test_non_owner_can_view_public_archive_metadata_overrides(self):
601+ user = self.factory.makePerson()
602+ archive = self.create_archive()
603+ login_person(user)
604+ self.assertEqual(
605+ archive.metadata_overrides,
606+ self.default_metadata_overrides,
607+ )
608+
609+ def test_owner_can_view_own_public_archive_metadata_overrides(self):
610+ owner = self.factory.makePerson()
611+ archive = self.create_archive(owner=owner)
612+ login_person(owner)
613+ self.assertEqual(
614+ archive.metadata_overrides,
615+ self.default_metadata_overrides,
616+ )
617+
618+ def test_admin_can_view_metadata_overrides_of_any_public_archive(self):
619+ archive = self.create_archive()
620+ login_celebrity("admin")
621+ self.assertEqual(
622+ archive.metadata_overrides,
623+ self.default_metadata_overrides,
624+ )
625+
626+ def test_owner_can_view_own_private_archive_metadata_overrides(self):
627+ owner = self.factory.makePerson()
628+ private_archive = self.create_archive(owner=owner, private=True)
629+ login_person(owner)
630+ self.assertEqual(
631+ private_archive.metadata_overrides,
632+ self.default_metadata_overrides,
633+ )
634+
635+ def test_admin_can_view_metadata_overrides_of_any_private_archive(self):
636+ private_archive = self.create_archive(private=True)
637+ login_celebrity("admin")
638+ self.assertEqual(
639+ private_archive.metadata_overrides,
640+ self.default_metadata_overrides,
641+ )
642+
643+ def test_anonymous_cannot_view_private_archive_metadata_overrides(self):
644+ private_archive = self.create_archive(private=True)
645+ login(ANONYMOUS)
646+ self.assertRaises(
647+ Unauthorized, getattr, private_archive, "metadata_overrides"
648+ )
649+
650+ def test_non_owner_cannot_view_private_archive_metadata_overrides(self):
651+ owner = self.factory.makePerson()
652+ user = self.factory.makePerson()
653+ private_archive = self.create_archive(owner=owner, private=True)
654+ login_person(user)
655+ self.assertRaises(
656+ Unauthorized, getattr, private_archive, "metadata_overrides"
657+ )
658+
659+ def test_subscriber_cannot_view_private_archive_metadata_overrides(self):
660+ owner = self.factory.makePerson()
661+ user = self.factory.makePerson()
662+ private_archive = self.create_archive(owner=owner, private=True)
663+ with person_logged_in(owner):
664+ private_archive.newSubscription(user, owner)
665+ login_person(user)
666+ self.assertRaises(
667+ Unauthorized, getattr, private_archive, "metadata_overrides"
668+ )
669+
670+ def test_owner_can_set_metadata_overrides_on_own_public_archive(self):
671+ owner = self.factory.makePerson()
672+ login_person(owner)
673+ archive = self.create_archive(owner=owner)
674+ self.assertEqual(
675+ archive.metadata_overrides, self.default_metadata_overrides
676+ )
677+ overrides = {"Origin": "test_origin"}
678+ archive.setMetadataOverrides(overrides)
679+ self.assertEqual(archive.metadata_overrides, overrides)
680+
681+ def test_admin_can_set_metadata_overrides_on_any_public_archive(self):
682+ archive = self.create_archive()
683+ login_celebrity("admin")
684+ self.assertEqual(
685+ archive.metadata_overrides, self.default_metadata_overrides
686+ )
687+ overrides = {"Origin": "test_origin"}
688+ archive.setMetadataOverrides(overrides)
689+ self.assertEqual(archive.metadata_overrides, overrides)
690+
691+ def test_anonymous_cannot_set_metadata_overrides_on_public_archive(self):
692+ archive = self.create_archive()
693+ login(ANONYMOUS)
694+ overrides = {"Origin": "test_origin"}
695+ self.assertRaises(
696+ Unauthorized, lambda: archive.setMetadataOverrides(overrides)
697+ )
698+
699+ def test_non_owner_cannot_set_metadata_overrides_on_public_archive(self):
700+ user = self.factory.makePerson()
701+ archive = self.create_archive()
702+ login_person(user)
703+ overrides = {"Origin": "test_origin"}
704+ self.assertRaises(
705+ Unauthorized, lambda: archive.setMetadataOverrides(overrides)
706+ )
707+
708+ def test_owner_can_set_metadata_overrides_on_private_archive(self):
709+ owner = self.factory.makePerson()
710+ login_person(owner)
711+ private_archive = self.create_archive(owner=owner, private=True)
712+ self.assertEqual(
713+ private_archive.metadata_overrides, self.default_metadata_overrides
714+ )
715+ overrides = {"Origin": "test_origin"}
716+ private_archive.setMetadataOverrides(overrides)
717+ self.assertEqual(private_archive.metadata_overrides, overrides)
718+
719+ def test_admin_can_set_metadata_overrides_on_any_private_archive(self):
720+ private_archive = self.create_archive(private=True)
721+ login_celebrity("admin")
722+ self.assertEqual(
723+ private_archive.metadata_overrides, self.default_metadata_overrides
724+ )
725+ overrides = {"Origin": "test_origin"}
726+ private_archive.setMetadataOverrides(overrides)
727+ self.assertEqual(private_archive.metadata_overrides, overrides)
728+
729+ def test_anonymous_cannot_set_metadata_overrides_on_private_archive(self):
730+ private_archive = self.create_archive(private=True)
731+ login(ANONYMOUS)
732+ overrides = {"Origin": "test_origin"}
733+ self.assertRaises(
734+ Unauthorized,
735+ lambda: private_archive.setMetadataOverrides(overrides),
736+ )
737+
738+ def test_non_owner_cannot_set_metadata_overrides_on_private_archive(self):
739+ user = self.factory.makePerson()
740+ private_archive = self.create_archive(private=True)
741+ login_person(user)
742+ overrides = {"Origin": "test_origin"}
743+ self.assertRaises(
744+ Unauthorized,
745+ lambda: private_archive.setMetadataOverrides(overrides),
746+ )
747+
748+ def test_subscriber_cannot_set_metadata_overrides_on_private_archive(self):
749+ owner = self.factory.makePerson()
750+ user = self.factory.makePerson()
751+ private_archive = self.create_archive(owner=owner, private=True)
752+ with person_logged_in(owner):
753+ private_archive.newSubscription(user, owner)
754+ login_person(user)
755+ overrides = {"Origin": "test_origin"}
756+ self.assertRaises(
757+ Unauthorized,
758+ lambda: private_archive.setMetadataOverrides(overrides),
759+ )
760+
761+ def test_owner_can_set_metadata_overrides_on_own_primary_archive(self):
762+ owner = self.factory.makePerson()
763+ primary_archive = self.create_archive(owner=owner, primary=True)
764+ login_person(owner)
765+ self.assertEqual(
766+ primary_archive.metadata_overrides, self.default_metadata_overrides
767+ )
768+ overrides = {"Origin": "test_origin"}
769+ primary_archive.setMetadataOverrides(overrides)
770+ self.assertEqual(primary_archive.metadata_overrides, overrides)
771+
772+ def test_admin_can_set_metadata_overrides_on_any_primary_archive(self):
773+ primary_archive = self.create_archive(primary=True)
774+ login_celebrity("admin")
775+ self.assertEqual(
776+ primary_archive.metadata_overrides, self.default_metadata_overrides
777+ )
778+ overrides = {"Origin": "test_origin"}
779+ primary_archive.setMetadataOverrides(overrides)
780+ self.assertEqual(primary_archive.metadata_overrides, overrides)
781+
782+ def test_anonymous_cannot_set_metadata_overrides_on_primary_archive(self):
783+ primary_archive = self.create_archive(primary=True)
784+ login(ANONYMOUS)
785+ overrides = {"Origin": "test_origin"}
786+ self.assertRaises(
787+ Unauthorized,
788+ lambda: primary_archive.setMetadataOverrides(overrides),
789+ )
790+
791+ def test_non_owner_cannot_set_metadata_overrides_on_primary_archive(self):
792+ user = self.factory.makePerson()
793+ primary_archive = self.create_archive(primary=True)
794+ login_person(user)
795+ overrides = {"Origin": "test_origin"}
796+ self.assertRaises(
797+ Unauthorized,
798+ lambda: primary_archive.setMetadataOverrides(overrides),
799+ )
800diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
801index b4c9bb1..6cdf9b4 100644
802--- a/lib/lp/testing/factory.py
803+++ b/lib/lp/testing/factory.py
804@@ -3706,6 +3706,7 @@ class LaunchpadObjectFactory(ObjectFactory):
805 processors=None,
806 publishing_method=ArchivePublishingMethod.LOCAL,
807 repository_format=ArchiveRepositoryFormat.DEBIAN,
808+ metadata_overrides=None,
809 ):
810 """Create and return a new arbitrary archive.
811
812@@ -3765,6 +3766,7 @@ class LaunchpadObjectFactory(ObjectFactory):
813 processors=processors,
814 publishing_method=publishing_method,
815 repository_format=repository_format,
816+ metadata_overrides=metadata_overrides,
817 )
818
819 if private:

Subscribers

People subscribed via source and target branches

to status/vote changes: