Merge ~pappacena/launchpad:oci-api-create-recipe into launchpad:master
- Git
- lp:~pappacena/launchpad
- oci-api-create-recipe
- Merge into master
Status: | Merged |
---|---|
Approved by: | Thiago F. Pappacena |
Approved revision: | ff742c693e21d53a35779f1aad946ed24df2ea8d |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~pappacena/launchpad:oci-api-create-recipe |
Merge into: | launchpad:master |
Prerequisite: | ~pappacena/launchpad:oci-project-api |
Diff against target: |
842 lines (+308/-47) 15 files modified
lib/lp/_schema_circular_imports.py (+1/-0) lib/lp/oci/browser/ocirecipe.py (+9/-1) lib/lp/oci/browser/tests/test_ocirecipe.py (+23/-0) lib/lp/oci/browser/tests/test_ocirecipebuild.py (+7/-0) lib/lp/oci/interfaces/ocirecipe.py (+13/-1) lib/lp/oci/model/ocirecipe.py (+10/-3) lib/lp/oci/tests/helpers.py (+5/-0) lib/lp/oci/tests/test_ocirecipe.py (+135/-32) lib/lp/oci/tests/test_ocirecipebuild.py (+11/-2) lib/lp/oci/tests/test_ocirecipebuildbehaviour.py (+13/-2) lib/lp/registry/configure.zcml (+3/-0) lib/lp/registry/interfaces/ociproject.py (+49/-2) lib/lp/registry/model/ociproject.py (+16/-0) lib/lp/registry/tests/test_personmerge.py (+7/-2) lib/lp/services/webhooks/tests/test_job.py (+6/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+381065@code.launchpad.net |
Commit message
API operation to create a new OCIRecipe from an existing OCIProject.
The feature is only enabled if we turn on the 'oci.recipe.
Description of the change
This MP includes code from https:/
Thiago F. Pappacena (pappacena) : | # |
- 4171065... by Thiago F. Pappacena
-
Merge branch 'master' into oci-api-
create- recipe
Colin Watson (cjwatson) : | # |
- 719b074... by Thiago F. Pappacena
-
Refactoring tests
- 222602a... by Thiago F. Pappacena
-
Removing unused parameter
- d14a4c6... by Thiago F. Pappacena
-
Moving OCI_RECIPE_
ALLOW_CREATE feature flag to OCIRecipe.__init__ and its view initializer - 75e9047... by Thiago F. Pappacena
-
Renaming IOCIProjectPubl
icActions interface to IOCIProjectLegi timate - c979846... by Thiago F. Pappacena
-
OCIRecipe owner should be provided on creation when using API
- 5bc0691... by Thiago F. Pappacena
-
Adding build_daily on OCI Recipe creation API
Thiago F. Pappacena (pappacena) wrote : | # |
- d227d91... by Thiago F. Pappacena
-
Fixing tests to use feature flag
Thiago F. Pappacena (pappacena) wrote : | # |
Ah, I'm actually missing one of the requests. I'll push in some minutes.
- fd4fa2e... by Thiago F. Pappacena
-
Actually using build_daily on OCIRecipe's create UI
- a258de0... by Thiago F. Pappacena
-
Merge branch 'master' into oci-api-
create- recipe - efc1f7f... by Thiago F. Pappacena
-
Merge branch 'master' into oci-api-
create- recipe
Thiago F. Pappacena (pappacena) wrote : | # |
Now I think it's done, and ready for another round of review.
Sorry for the long diff, but most of the new changes are just adding the FeatureFixture on old tests.
Colin Watson (cjwatson) : | # |
Colin Watson (cjwatson) : | # |
- ff742c6... by Thiago F. Pappacena
-
Minor refactoring and adjusting constraing on OCIProject.
newRecipe
Thiago F. Pappacena (pappacena) wrote : | # |
Thanks for the review. All requested changes were made. I'll top-approve this MP now.
Preview Diff
1 | diff --git a/lib/lp/_schema_circular_imports.py b/lib/lp/_schema_circular_imports.py |
2 | index dd4e9d4..37369b4 100644 |
3 | --- a/lib/lp/_schema_circular_imports.py |
4 | +++ b/lib/lp/_schema_circular_imports.py |
5 | @@ -1103,6 +1103,7 @@ patch_entry_explicit_version(IWikiName, 'beta') |
6 | |
7 | # IOCIProject |
8 | patch_collection_property(IOCIProject, 'series', IOCIProjectSeries) |
9 | +patch_entry_return_type(IOCIProject, 'newRecipe', IOCIRecipe) |
10 | |
11 | # IOCIRecipe |
12 | patch_collection_property(IOCIRecipe, 'builds', IOCIRecipeBuild) |
13 | diff --git a/lib/lp/oci/browser/ocirecipe.py b/lib/lp/oci/browser/ocirecipe.py |
14 | index 7d044f5..10e9d4b 100644 |
15 | --- a/lib/lp/oci/browser/ocirecipe.py |
16 | +++ b/lib/lp/oci/browser/ocirecipe.py |
17 | @@ -35,7 +35,9 @@ from lp.oci.interfaces.ocirecipe import ( |
18 | IOCIRecipe, |
19 | IOCIRecipeSet, |
20 | NoSuchOCIRecipe, |
21 | + OCI_RECIPE_ALLOW_CREATE, |
22 | OCI_RECIPE_WEBHOOKS_FEATURE_FLAG, |
23 | + OCIRecipeFeatureDisabled, |
24 | ) |
25 | from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet |
26 | from lp.services.features import getFeatureFlag |
27 | @@ -174,6 +176,11 @@ class OCIRecipeAddView(LaunchpadFormView): |
28 | ) |
29 | custom_widget_git_ref = GitRefWidget |
30 | |
31 | + def initialize(self): |
32 | + super(OCIRecipeAddView, self).initialize() |
33 | + if not getFeatureFlag(OCI_RECIPE_ALLOW_CREATE): |
34 | + raise OCIRecipeFeatureDisabled() |
35 | + |
36 | @property |
37 | def cancel_url(self): |
38 | """See `LaunchpadFormView`.""" |
39 | @@ -205,7 +212,8 @@ class OCIRecipeAddView(LaunchpadFormView): |
40 | recipe = getUtility(IOCIRecipeSet).new( |
41 | name=data["name"], registrant=self.user, owner=data["owner"], |
42 | oci_project=self.context, git_ref=data["git_ref"], |
43 | - build_file=data["build_file"], description=data["description"]) |
44 | + build_file=data["build_file"], description=data["description"], |
45 | + build_daily=data["build_daily"]) |
46 | self.next_url = canonical_url(recipe) |
47 | |
48 | |
49 | diff --git a/lib/lp/oci/browser/tests/test_ocirecipe.py b/lib/lp/oci/browser/tests/test_ocirecipe.py |
50 | index 540649f..bd87975 100644 |
51 | --- a/lib/lp/oci/browser/tests/test_ocirecipe.py |
52 | +++ b/lib/lp/oci/browser/tests/test_ocirecipe.py |
53 | @@ -29,7 +29,9 @@ from lp.oci.browser.ocirecipe import ( |
54 | OCIRecipeEditView, |
55 | OCIRecipeView, |
56 | ) |
57 | +from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE |
58 | from lp.services.database.constants import UTC_NOW |
59 | +from lp.services.features.testing import FeatureFixture |
60 | from lp.services.propertycache import get_property_cache |
61 | from lp.services.webapp import canonical_url |
62 | from lp.services.webapp.servers import LaunchpadTestRequest |
63 | @@ -62,6 +64,10 @@ class TestOCIRecipeNavigation(TestCaseWithFactory): |
64 | |
65 | layer = DatabaseFunctionalLayer |
66 | |
67 | + def setUp(self): |
68 | + super(TestOCIRecipeNavigation, self).setUp() |
69 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
70 | + |
71 | def test_canonical_url(self): |
72 | owner = self.factory.makePerson(name="person") |
73 | distribution = self.factory.makeDistribution(name="distro") |
74 | @@ -96,6 +102,10 @@ class BaseTestOCIRecipeView(BrowserTestCase): |
75 | |
76 | class TestOCIRecipeAddView(BaseTestOCIRecipeView): |
77 | |
78 | + def setUp(self): |
79 | + super(TestOCIRecipeAddView, self).setUp() |
80 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
81 | + |
82 | def test_create_new_recipe_not_logged_in(self): |
83 | oci_project = self.factory.makeOCIProject() |
84 | self.assertRaises( |
85 | @@ -151,6 +161,10 @@ class TestOCIRecipeAddView(BaseTestOCIRecipeView): |
86 | |
87 | class TestOCIRecipeAdminView(BaseTestOCIRecipeView): |
88 | |
89 | + def setUp(self): |
90 | + super(TestOCIRecipeAdminView, self).setUp() |
91 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
92 | + |
93 | def test_unauthorized(self): |
94 | # A non-admin user cannot administer an OCI recipe. |
95 | login_person(self.person) |
96 | @@ -199,6 +213,10 @@ class TestOCIRecipeAdminView(BaseTestOCIRecipeView): |
97 | |
98 | class TestOCIRecipeEditView(BaseTestOCIRecipeView): |
99 | |
100 | + def setUp(self): |
101 | + super(TestOCIRecipeEditView, self).setUp() |
102 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
103 | + |
104 | def test_edit_recipe(self): |
105 | oci_project = self.factory.makeOCIProject() |
106 | oci_project_display = oci_project.display_name |
107 | @@ -275,6 +293,10 @@ class TestOCIRecipeEditView(BaseTestOCIRecipeView): |
108 | |
109 | class TestOCIRecipeDeleteView(BaseTestOCIRecipeView): |
110 | |
111 | + def setUp(self): |
112 | + super(TestOCIRecipeDeleteView, self).setUp() |
113 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
114 | + |
115 | def test_unauthorized(self): |
116 | # A user without edit access cannot delete an OCI recipe. |
117 | recipe = self.factory.makeOCIRecipe( |
118 | @@ -326,6 +348,7 @@ class TestOCIRecipeView(BaseTestOCIRecipeView): |
119 | distroseries=self.distroseries, architecturetag="i386", |
120 | processor=processor) |
121 | self.factory.makeBuilder(virtualized=True) |
122 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
123 | |
124 | def makeOCIRecipe(self, oci_project=None, **kwargs): |
125 | if oci_project is None: |
126 | diff --git a/lib/lp/oci/browser/tests/test_ocirecipebuild.py b/lib/lp/oci/browser/tests/test_ocirecipebuild.py |
127 | index ed3cb9b..72f29e9 100644 |
128 | --- a/lib/lp/oci/browser/tests/test_ocirecipebuild.py |
129 | +++ b/lib/lp/oci/browser/tests/test_ocirecipebuild.py |
130 | @@ -13,6 +13,8 @@ from storm.locals import Store |
131 | from testtools.matchers import StartsWith |
132 | |
133 | from lp.buildmaster.enums import BuildStatus |
134 | +from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE |
135 | +from lp.services.features.testing import FeatureFixture |
136 | from lp.services.webapp import canonical_url |
137 | from lp.testing import ( |
138 | BrowserTestCase, |
139 | @@ -29,6 +31,10 @@ class TestCanonicalUrlForOCIRecipeBuild(TestCaseWithFactory): |
140 | |
141 | layer = DatabaseFunctionalLayer |
142 | |
143 | + def setUp(self): |
144 | + super(TestCanonicalUrlForOCIRecipeBuild, self).setUp() |
145 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
146 | + |
147 | def test_canonical_url(self): |
148 | owner = self.factory.makePerson(name="person") |
149 | distribution = self.factory.makeDistribution(name="distro") |
150 | @@ -51,6 +57,7 @@ class TestOCIRecipeBuildOperations(BrowserTestCase): |
151 | |
152 | def setUp(self): |
153 | super(TestOCIRecipeBuildOperations, self).setUp() |
154 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
155 | self.build = self.factory.makeOCIRecipeBuild() |
156 | self.build_url = canonical_url(self.build) |
157 | |
158 | diff --git a/lib/lp/oci/interfaces/ocirecipe.py b/lib/lp/oci/interfaces/ocirecipe.py |
159 | index 99a0719..ca70e38 100644 |
160 | --- a/lib/lp/oci/interfaces/ocirecipe.py |
161 | +++ b/lib/lp/oci/interfaces/ocirecipe.py |
162 | @@ -15,8 +15,10 @@ __all__ = [ |
163 | 'IOCIRecipeView', |
164 | 'NoSourceForOCIRecipe', |
165 | 'NoSuchOCIRecipe', |
166 | + 'OCI_RECIPE_ALLOW_CREATE', |
167 | 'OCI_RECIPE_WEBHOOKS_FEATURE_FLAG', |
168 | 'OCIRecipeBuildAlreadyPending', |
169 | + 'OCIRecipeFeatureDisabled', |
170 | 'OCIRecipeNotOwner', |
171 | ] |
172 | |
173 | @@ -58,6 +60,16 @@ from lp.services.webhooks.interfaces import IWebhookTarget |
174 | |
175 | |
176 | OCI_RECIPE_WEBHOOKS_FEATURE_FLAG = "oci.recipe.webhooks.enabled" |
177 | +OCI_RECIPE_ALLOW_CREATE = 'oci.recipe.create.enabled' |
178 | + |
179 | + |
180 | +@error_status(http_client.UNAUTHORIZED) |
181 | +class OCIRecipeFeatureDisabled(Unauthorized): |
182 | + """Only certain users can create new LiveFS-related objects.""" |
183 | + |
184 | + def __init__(self): |
185 | + super(OCIRecipeFeatureDisabled, self).__init__( |
186 | + "You do not have permission to create new OCI recipe.") |
187 | |
188 | |
189 | @error_status(http_client.UNAUTHORIZED) |
190 | @@ -255,7 +267,7 @@ class IOCIRecipeSet(Interface): |
191 | |
192 | def new(name, registrant, owner, oci_project, git_ref, build_file, |
193 | description=None, official=False, require_virtualized=True, |
194 | - date_created=DEFAULT): |
195 | + build_daily=False, date_created=DEFAULT): |
196 | """Create an IOCIRecipe.""" |
197 | |
198 | def exists(owner, oci_project, name): |
199 | diff --git a/lib/lp/oci/model/ocirecipe.py b/lib/lp/oci/model/ocirecipe.py |
200 | index 68f5e92..21a936b 100644 |
201 | --- a/lib/lp/oci/model/ocirecipe.py |
202 | +++ b/lib/lp/oci/model/ocirecipe.py |
203 | @@ -42,7 +42,9 @@ from lp.oci.interfaces.ocirecipe import ( |
204 | IOCIRecipeSet, |
205 | NoSourceForOCIRecipe, |
206 | NoSuchOCIRecipe, |
207 | + OCI_RECIPE_ALLOW_CREATE, |
208 | OCIRecipeBuildAlreadyPending, |
209 | + OCIRecipeFeatureDisabled, |
210 | OCIRecipeNotOwner, |
211 | ) |
212 | from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet |
213 | @@ -62,6 +64,7 @@ from lp.services.database.stormexpr import ( |
214 | Greatest, |
215 | NullsLast, |
216 | ) |
217 | +from lp.services.features import getFeatureFlag |
218 | from lp.services.webhooks.interfaces import IWebhookSet |
219 | from lp.services.webhooks.model import WebhookTargetMixin |
220 | |
221 | @@ -112,7 +115,9 @@ class OCIRecipe(Storm, WebhookTargetMixin): |
222 | |
223 | def __init__(self, name, registrant, owner, oci_project, git_ref, |
224 | description=None, official=False, require_virtualized=True, |
225 | - build_file=None, date_created=DEFAULT): |
226 | + build_file=None, build_daily=False, date_created=DEFAULT): |
227 | + if not getFeatureFlag(OCI_RECIPE_ALLOW_CREATE): |
228 | + raise OCIRecipeFeatureDisabled() |
229 | super(OCIRecipe, self).__init__() |
230 | self.name = name |
231 | self.registrant = registrant |
232 | @@ -122,6 +127,7 @@ class OCIRecipe(Storm, WebhookTargetMixin): |
233 | self.build_file = build_file |
234 | self.official = official |
235 | self.require_virtualized = require_virtualized |
236 | + self.build_daily = build_daily |
237 | self.date_created = date_created |
238 | self.date_last_modified = date_created |
239 | self.git_ref = git_ref |
240 | @@ -276,7 +282,7 @@ class OCIRecipeSet: |
241 | |
242 | def new(self, name, registrant, owner, oci_project, git_ref, build_file, |
243 | description=None, official=False, require_virtualized=True, |
244 | - date_created=DEFAULT): |
245 | + build_daily=False, date_created=DEFAULT): |
246 | """See `IOCIRecipeSet`.""" |
247 | if not registrant.inTeam(owner): |
248 | if owner.is_team: |
249 | @@ -297,7 +303,8 @@ class OCIRecipeSet: |
250 | store = IMasterStore(OCIRecipe) |
251 | oci_recipe = OCIRecipe( |
252 | name, registrant, owner, oci_project, git_ref, description, |
253 | - official, require_virtualized, build_file, date_created) |
254 | + official, require_virtualized, build_file, build_daily, |
255 | + date_created) |
256 | store.add(oci_recipe) |
257 | |
258 | return oci_recipe |
259 | diff --git a/lib/lp/oci/tests/helpers.py b/lib/lp/oci/tests/helpers.py |
260 | index dc0e82f..e199241 100644 |
261 | --- a/lib/lp/oci/tests/helpers.py |
262 | +++ b/lib/lp/oci/tests/helpers.py |
263 | @@ -12,6 +12,9 @@ import base64 |
264 | |
265 | from nacl.public import PrivateKey |
266 | |
267 | +from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE |
268 | +from lp.services.features.testing import FeatureFixture |
269 | + |
270 | |
271 | class OCIConfigHelperMixin: |
272 | |
273 | @@ -25,3 +28,5 @@ class OCIConfigHelperMixin: |
274 | "oci", |
275 | registry_secrets_private_key=base64.b64encode( |
276 | bytes(self.private_key)).decode("UTF-8")) |
277 | + # Default feature flags for our tests |
278 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
279 | diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py |
280 | index 3c60e7e..e15dd8e 100644 |
281 | --- a/lib/lp/oci/tests/test_ocirecipe.py |
282 | +++ b/lib/lp/oci/tests/test_ocirecipe.py |
283 | @@ -9,8 +9,8 @@ import base64 |
284 | import json |
285 | |
286 | from fixtures import FakeLogger |
287 | -from six import string_types |
288 | from nacl.public import PrivateKey |
289 | +from six import string_types |
290 | from storm.exceptions import LostObjectError |
291 | from testtools.matchers import ( |
292 | ContainsDict, |
293 | @@ -29,6 +29,7 @@ from lp.oci.interfaces.ocirecipe import ( |
294 | IOCIRecipeSet, |
295 | NoSourceForOCIRecipe, |
296 | NoSuchOCIRecipe, |
297 | + OCI_RECIPE_ALLOW_CREATE, |
298 | OCI_RECIPE_WEBHOOKS_FEATURE_FLAG, |
299 | OCIRecipeBuildAlreadyPending, |
300 | OCIRecipeNotOwner, |
301 | @@ -60,6 +61,10 @@ class TestOCIRecipe(TestCaseWithFactory): |
302 | |
303 | layer = DatabaseFunctionalLayer |
304 | |
305 | + def setUp(self): |
306 | + super(TestOCIRecipe, self).setUp() |
307 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
308 | + |
309 | def test_implements_interface(self): |
310 | target = self.factory.makeOCIRecipe() |
311 | with admin_logged_in(): |
312 | @@ -106,7 +111,8 @@ class TestOCIRecipe(TestCaseWithFactory): |
313 | def test_requestBuild_triggers_webhooks(self): |
314 | # Requesting a build triggers webhooks. |
315 | logger = self.useFixture(FakeLogger()) |
316 | - with FeatureFixture({OCI_RECIPE_WEBHOOKS_FEATURE_FLAG: "on"}): |
317 | + with FeatureFixture({OCI_RECIPE_WEBHOOKS_FEATURE_FLAG: "on", |
318 | + OCI_RECIPE_ALLOW_CREATE: 'on'}): |
319 | recipe = self.factory.makeOCIRecipe() |
320 | oci_arch = self.factory.makeOCIRecipeArch(recipe=recipe) |
321 | hook = self.factory.makeWebhook( |
322 | @@ -153,7 +159,8 @@ class TestOCIRecipe(TestCaseWithFactory): |
323 | |
324 | def test_related_webhooks_deleted(self): |
325 | owner = self.factory.makePerson() |
326 | - with FeatureFixture({OCI_RECIPE_WEBHOOKS_FEATURE_FLAG: "on"}): |
327 | + with FeatureFixture({OCI_RECIPE_WEBHOOKS_FEATURE_FLAG: "on", |
328 | + OCI_RECIPE_ALLOW_CREATE: 'on'}): |
329 | recipe = self.factory.makeOCIRecipe(registrant=owner, owner=owner) |
330 | webhook = self.factory.makeWebhook(target=recipe) |
331 | with person_logged_in(recipe.owner): |
332 | @@ -214,6 +221,10 @@ class TestOCIRecipeSet(TestCaseWithFactory): |
333 | |
334 | layer = DatabaseFunctionalLayer |
335 | |
336 | + def setUp(self): |
337 | + super(TestOCIRecipeSet, self).setUp() |
338 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
339 | + |
340 | def test_implements_interface(self): |
341 | target_set = getUtility(IOCIRecipeSet) |
342 | with admin_logged_in(): |
343 | @@ -382,10 +393,12 @@ class TestOCIRecipeWebservice(TestCaseWithFactory): |
344 | |
345 | def setUp(self): |
346 | super(TestOCIRecipeWebservice, self).setUp() |
347 | - self.person = self.factory.makePerson(displayname="Test Person") |
348 | + self.person = self.factory.makePerson( |
349 | + displayname="Test Person") |
350 | self.webservice = webservice_for_person( |
351 | self.person, permission=OAuthPermission.WRITE_PUBLIC, |
352 | default_api_version="devel") |
353 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
354 | |
355 | def getAbsoluteURL(self, target): |
356 | """Get the webservice absolute URL of the given object or relative |
357 | @@ -401,38 +414,39 @@ class TestOCIRecipeWebservice(TestCaseWithFactory): |
358 | |
359 | def test_api_get_oci_recipe(self): |
360 | with person_logged_in(self.person): |
361 | - project = removeSecurityProxy(self.factory.makeOCIProject( |
362 | - registrant=self.person)) |
363 | - recipe = removeSecurityProxy(self.factory.makeOCIRecipe( |
364 | - oci_project=project)) |
365 | + oci_project = self.factory.makeOCIProject( |
366 | + registrant=self.person) |
367 | + recipe = self.factory.makeOCIRecipe( |
368 | + oci_project=oci_project) |
369 | url = api_url(recipe) |
370 | |
371 | ws_recipe = self.load_from_api(url) |
372 | |
373 | - recipe_abs_url = self.getAbsoluteURL(recipe) |
374 | - self.assertThat(ws_recipe, ContainsDict(dict( |
375 | - date_created=Equals(recipe.date_created.isoformat()), |
376 | - date_last_modified=Equals(recipe.date_last_modified.isoformat()), |
377 | - registrant_link=Equals(self.getAbsoluteURL(recipe.registrant)), |
378 | - webhooks_collection_link=Equals(recipe_abs_url + "/webhooks"), |
379 | - name=Equals(recipe.name), |
380 | - owner_link=Equals(self.getAbsoluteURL(recipe.owner)), |
381 | - oci_project_link=Equals(self.getAbsoluteURL(project)), |
382 | - git_ref_link=Equals(self.getAbsoluteURL(recipe.git_ref)), |
383 | - description=Equals(recipe.description), |
384 | - build_file=Equals(recipe.build_file), |
385 | - build_daily=Equals(recipe.build_daily) |
386 | - ))) |
387 | + with person_logged_in(self.person): |
388 | + recipe_abs_url = self.getAbsoluteURL(recipe) |
389 | + self.assertThat(ws_recipe, ContainsDict(dict( |
390 | + date_created=Equals(recipe.date_created.isoformat()), |
391 | + date_last_modified=Equals(recipe.date_last_modified.isoformat()), |
392 | + registrant_link=Equals(self.getAbsoluteURL(recipe.registrant)), |
393 | + webhooks_collection_link=Equals(recipe_abs_url + "/webhooks"), |
394 | + name=Equals(recipe.name), |
395 | + owner_link=Equals(self.getAbsoluteURL(recipe.owner)), |
396 | + oci_project_link=Equals(self.getAbsoluteURL(oci_project)), |
397 | + git_ref_link=Equals(self.getAbsoluteURL(recipe.git_ref)), |
398 | + description=Equals(recipe.description), |
399 | + build_file=Equals(recipe.build_file), |
400 | + build_daily=Equals(recipe.build_daily) |
401 | + ))) |
402 | |
403 | def test_api_patch_oci_recipe(self): |
404 | with person_logged_in(self.person): |
405 | distro = self.factory.makeDistribution(owner=self.person) |
406 | - project = removeSecurityProxy(self.factory.makeOCIProject( |
407 | - pillar=distro, registrant=self.person)) |
408 | + oci_project = self.factory.makeOCIProject( |
409 | + pillar=distro, registrant=self.person) |
410 | # Only the owner should be able to edit. |
411 | - recipe = removeSecurityProxy(self.factory.makeOCIRecipe( |
412 | - oci_project=project, owner=self.person, |
413 | - registrant=self.person)) |
414 | + recipe = self.factory.makeOCIRecipe( |
415 | + oci_project=oci_project, owner=self.person, |
416 | + registrant=self.person) |
417 | url = api_url(recipe) |
418 | |
419 | new_description = 'Some other description' |
420 | @@ -450,13 +464,13 @@ class TestOCIRecipeWebservice(TestCaseWithFactory): |
421 | other_person = self.factory.makePerson() |
422 | with person_logged_in(other_person): |
423 | distro = self.factory.makeDistribution(owner=other_person) |
424 | - project = removeSecurityProxy(self.factory.makeOCIProject( |
425 | - pillar=distro, registrant=other_person)) |
426 | + oci_project = self.factory.makeOCIProject( |
427 | + pillar=distro, registrant=other_person) |
428 | # Only the owner should be able to edit. |
429 | - recipe = removeSecurityProxy(self.factory.makeOCIRecipe( |
430 | - oci_project=project, owner=other_person, |
431 | + recipe = self.factory.makeOCIRecipe( |
432 | + oci_project=oci_project, owner=other_person, |
433 | registrant=other_person, |
434 | - description="old description")) |
435 | + description="old description") |
436 | url = api_url(recipe) |
437 | |
438 | new_description = 'Some other description' |
439 | @@ -467,3 +481,92 @@ class TestOCIRecipeWebservice(TestCaseWithFactory): |
440 | |
441 | ws_project = self.load_from_api(url) |
442 | self.assertEqual("old description", ws_project['description']) |
443 | + |
444 | + def test_api_create_oci_recipe(self): |
445 | + with person_logged_in(self.person): |
446 | + distro = self.factory.makeDistribution( |
447 | + owner=self.person) |
448 | + oci_project = self.factory.makeOCIProject( |
449 | + pillar=distro, registrant=self.person) |
450 | + git_ref = self.factory.makeGitRefs()[0] |
451 | + |
452 | + oci_project_url = api_url(oci_project) |
453 | + git_ref_url = api_url(git_ref) |
454 | + person_url = api_url(self.person) |
455 | + |
456 | + obj = { |
457 | + "name": "my-recipe", |
458 | + "owner": person_url, |
459 | + "git_ref": git_ref_url, |
460 | + "build_file": "./Dockerfile", |
461 | + "description": "My recipe"} |
462 | + |
463 | + resp = self.webservice.named_post(oci_project_url, "newRecipe", **obj) |
464 | + self.assertEqual(201, resp.status, resp.body) |
465 | + |
466 | + new_obj_url = resp.getHeader("Location") |
467 | + ws_recipe = self.load_from_api(new_obj_url) |
468 | + |
469 | + with person_logged_in(self.person): |
470 | + self.assertThat(ws_recipe, ContainsDict(dict( |
471 | + name=Equals(obj["name"]), |
472 | + oci_project_link=Equals(self.getAbsoluteURL(oci_project)), |
473 | + git_ref_link=Equals(self.getAbsoluteURL(git_ref)), |
474 | + build_file=Equals(obj["build_file"]), |
475 | + description=Equals(obj["description"]), |
476 | + owner_link=Equals(self.getAbsoluteURL(self.person)), |
477 | + registrant_link=Equals(self.getAbsoluteURL(self.person)), |
478 | + ))) |
479 | + |
480 | + def test_api_create_oci_recipe_non_legitimate_user(self): |
481 | + """Ensure that a non-legitimate user cannot create recipe using API""" |
482 | + self.pushConfig( |
483 | + 'launchpad', min_legitimate_karma=9999, |
484 | + min_legitimate_account_age=9999) |
485 | + |
486 | + with person_logged_in(self.person): |
487 | + distro = self.factory.makeDistribution( |
488 | + owner=self.person) |
489 | + oci_project = self.factory.makeOCIProject( |
490 | + pillar=distro, registrant=self.person) |
491 | + git_ref = self.factory.makeGitRefs()[0] |
492 | + |
493 | + oci_project_url = api_url(oci_project) |
494 | + git_ref_url = api_url(git_ref) |
495 | + person_url = api_url(self.person) |
496 | + |
497 | + obj = { |
498 | + "name": "My recipe", |
499 | + "owner": person_url, |
500 | + "git_ref": git_ref_url, |
501 | + "build_file": "./Dockerfile", |
502 | + "description": "My recipe"} |
503 | + |
504 | + resp = self.webservice.named_post(oci_project_url, "newRecipe", **obj) |
505 | + self.assertEqual(401, resp.status, resp.body) |
506 | + |
507 | + def test_api_create_oci_recipe_is_disabled_by_feature_flag(self): |
508 | + """Ensure that OCI newRecipe API method returns HTTP 401 when the |
509 | + feature flag is not set.""" |
510 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: ''})) |
511 | + |
512 | + with person_logged_in(self.person): |
513 | + distro = self.factory.makeDistribution( |
514 | + owner=self.person) |
515 | + oci_project = self.factory.makeOCIProject( |
516 | + pillar=distro, registrant=self.person) |
517 | + git_ref = self.factory.makeGitRefs()[0] |
518 | + |
519 | + oci_project_url = api_url(oci_project) |
520 | + git_ref_url = api_url(git_ref) |
521 | + person_url = api_url(self.person) |
522 | + |
523 | + obj = { |
524 | + "name": "My recipe", |
525 | + "owner": person_url, |
526 | + "git_ref": git_ref_url, |
527 | + "build_file": "./Dockerfile", |
528 | + "description": "My recipe"} |
529 | + |
530 | + resp = self.webservice.named_post(oci_project_url, "newRecipe", **obj) |
531 | + self.assertEqual(401, resp.status, resp.body) |
532 | diff --git a/lib/lp/oci/tests/test_ocirecipebuild.py b/lib/lp/oci/tests/test_ocirecipebuild.py |
533 | index eec8850..e60a362 100644 |
534 | --- a/lib/lp/oci/tests/test_ocirecipebuild.py |
535 | +++ b/lib/lp/oci/tests/test_ocirecipebuild.py |
536 | @@ -23,7 +23,10 @@ from lp.buildmaster.enums import BuildStatus |
537 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue |
538 | from lp.buildmaster.interfaces.packagebuild import IPackageBuild |
539 | from lp.buildmaster.interfaces.processor import IProcessorSet |
540 | -from lp.oci.interfaces.ocirecipe import OCI_RECIPE_WEBHOOKS_FEATURE_FLAG |
541 | +from lp.oci.interfaces.ocirecipe import ( |
542 | + OCI_RECIPE_ALLOW_CREATE, |
543 | + OCI_RECIPE_WEBHOOKS_FEATURE_FLAG, |
544 | + ) |
545 | from lp.oci.interfaces.ocirecipebuild import ( |
546 | IOCIRecipeBuild, |
547 | IOCIRecipeBuildSet, |
548 | @@ -54,6 +57,7 @@ class TestOCIRecipeBuild(TestCaseWithFactory): |
549 | |
550 | def setUp(self): |
551 | super(TestOCIRecipeBuild, self).setUp() |
552 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
553 | self.build = self.factory.makeOCIRecipeBuild() |
554 | |
555 | def test_implements_interface(self): |
556 | @@ -212,6 +216,10 @@ class TestOCIRecipeBuildSet(TestCaseWithFactory): |
557 | |
558 | layer = DatabaseFunctionalLayer |
559 | |
560 | + def setUp(self): |
561 | + super(TestOCIRecipeBuildSet, self).setUp() |
562 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
563 | + |
564 | def test_implements_interface(self): |
565 | target = OCIRecipeBuildSet() |
566 | with admin_logged_in(): |
567 | @@ -241,7 +249,8 @@ class TestOCIRecipeBuildSet(TestCaseWithFactory): |
568 | distribution=distribution, status=SeriesStatus.CURRENT) |
569 | processor = getUtility(IProcessorSet).getByName("386") |
570 | self.useFixture(FeatureFixture({ |
571 | - "oci.build_series.%s" % distribution.name: distroseries.name})) |
572 | + "oci.build_series.%s" % distribution.name: distroseries.name, |
573 | + OCI_RECIPE_ALLOW_CREATE: 'on'})) |
574 | distro_arch_series = self.factory.makeDistroArchSeries( |
575 | distroseries=distroseries, architecturetag="i386", |
576 | processor=processor) |
577 | diff --git a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py |
578 | index 58b6adc..1d9328b 100644 |
579 | --- a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py |
580 | +++ b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py |
581 | @@ -1,4 +1,4 @@ |
582 | -# Copyright 2015-2019 Canonical Ltd. This software is licensed under the |
583 | +# Copyright 2015-2020 Canonical Ltd. This software is licensed under the |
584 | # GNU Affero General Public License version 3 (see the file LICENSE). |
585 | |
586 | """Tests for `OCIRecipeBuildBehaviour`.""" |
587 | @@ -63,6 +63,7 @@ from lp.buildmaster.tests.snapbuildproxy import ( |
588 | from lp.buildmaster.tests.test_buildfarmjobbehaviour import ( |
589 | TestGetUploadMethodsMixin, |
590 | ) |
591 | +from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE |
592 | from lp.oci.model.ocirecipebuildbehaviour import OCIRecipeBuildBehaviour |
593 | from lp.registry.interfaces.series import SeriesStatus |
594 | from lp.services.config import config |
595 | @@ -123,6 +124,10 @@ class TestOCIBuildBehaviour(TestCaseWithFactory): |
596 | |
597 | layer = LaunchpadZopelessLayer |
598 | |
599 | + def setUp(self): |
600 | + super(TestOCIBuildBehaviour, self).setUp() |
601 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
602 | + |
603 | def test_provides_interface(self): |
604 | # OCIRecipeBuildBehaviour provides IBuildFarmJobBehaviour. |
605 | job = OCIRecipeBuildBehaviour(self.factory.makeOCIRecipeBuild()) |
606 | @@ -159,6 +164,7 @@ class TestAsyncOCIRecipeBuildBehaviour(MakeOCIBuildMixin, TestCaseWithFactory): |
607 | self.now = time.time() |
608 | self.useFixture(fixtures.MockPatch( |
609 | "time.time", return_value=self.now)) |
610 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
611 | |
612 | @defer.inlineCallbacks |
613 | def test_composeBuildRequest(self): |
614 | @@ -350,7 +356,8 @@ class TestAsyncOCIRecipeBuildBehaviour(MakeOCIBuildMixin, TestCaseWithFactory): |
615 | distribution=distribution, status=SeriesStatus.CURRENT) |
616 | processor = getUtility(IProcessorSet).getByName("386") |
617 | self.useFixture(FeatureFixture({ |
618 | - "oci.build_series.%s" % distribution.name: distroseries.name})) |
619 | + "oci.build_series.%s" % distribution.name: distroseries.name, |
620 | + OCI_RECIPE_ALLOW_CREATE: 'on'})) |
621 | distro_arch_series = self.factory.makeDistroArchSeries( |
622 | distroseries=distroseries, architecturetag="i386", |
623 | processor=processor) |
624 | @@ -401,6 +408,7 @@ class TestHandleStatusForOCIRecipeBuild(MakeOCIBuildMixin, |
625 | def setUp(self): |
626 | super(TestHandleStatusForOCIRecipeBuild, self).setUp() |
627 | self.useFixture(fixtures.FakeLogger()) |
628 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
629 | self.build = self.makeBuild() |
630 | # For the moment, we require a builder for the build so that |
631 | # handleStatus_OK can get a reference to the slave. |
632 | @@ -627,3 +635,6 @@ class TestHandleStatusForOCIRecipeBuild(MakeOCIBuildMixin, |
633 | class TestGetUploadMethodsForOCIRecipeBuild( |
634 | MakeOCIBuildMixin, TestGetUploadMethodsMixin, TestCaseWithFactory): |
635 | """IPackageBuild.getUpload-related methods work with OCI recipe builds.""" |
636 | + def setUp(self): |
637 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
638 | + super(TestGetUploadMethodsForOCIRecipeBuild, self).setUp() |
639 | diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml |
640 | index d5875c5..f3bcd9f 100644 |
641 | --- a/lib/lp/registry/configure.zcml |
642 | +++ b/lib/lp/registry/configure.zcml |
643 | @@ -745,6 +745,9 @@ |
644 | permission="launchpad.Edit" |
645 | interface="lp.registry.interfaces.ociproject.IOCIProjectEdit" |
646 | set_schema="lp.registry.interfaces.ociproject.IOCIProjectEditableAttributes" /> |
647 | + <require |
648 | + permission="launchpad.AnyLegitimatePerson" |
649 | + interface="lp.registry.interfaces.ociproject.IOCIProjectLegitimate"/> |
650 | </class> |
651 | <subscriber |
652 | for="lp.registry.interfaces.ociproject.IOCIProject zope.lifecycleevent.interfaces.IObjectModifiedEvent" |
653 | diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py |
654 | index b39c71f..8a3e734 100644 |
655 | --- a/lib/lp/registry/interfaces/ociproject.py |
656 | +++ b/lib/lp/registry/interfaces/ociproject.py |
657 | @@ -13,16 +13,23 @@ __all__ = [ |
658 | ] |
659 | |
660 | from lazr.restful.declarations import ( |
661 | + call_with, |
662 | export_as_webservice_entry, |
663 | + export_factory_operation, |
664 | exported, |
665 | + operation_for_version, |
666 | + operation_parameters, |
667 | + REQUEST_USER, |
668 | ) |
669 | from lazr.restful.fields import ( |
670 | CollectionField, |
671 | Reference, |
672 | ReferenceChoice, |
673 | ) |
674 | +from lp.app.validators.path import path_does_not_escape |
675 | from zope.interface import Interface |
676 | from zope.schema import ( |
677 | + Bool, |
678 | Datetime, |
679 | Int, |
680 | Text, |
681 | @@ -32,12 +39,16 @@ from zope.schema import ( |
682 | from lp import _ |
683 | from lp.app.validators.name import name_validator |
684 | from lp.bugs.interfaces.bugtarget import IBugTarget |
685 | +from lp.code.interfaces.gitref import IGitRef |
686 | from lp.code.interfaces.hasgitrepositories import IHasGitRepositories |
687 | from lp.registry.interfaces.distribution import IDistribution |
688 | from lp.registry.interfaces.ociprojectname import IOCIProjectName |
689 | from lp.registry.interfaces.series import SeriesStatus |
690 | from lp.services.database.constants import DEFAULT |
691 | -from lp.services.fields import PublicPersonChoice |
692 | +from lp.services.fields import ( |
693 | + PersonChoice, |
694 | + PublicPersonChoice, |
695 | + ) |
696 | |
697 | |
698 | OCI_PROJECT_ALLOW_CREATE = 'oci.project.create.enabled' |
699 | @@ -105,8 +116,44 @@ class IOCIProjectEdit(Interface): |
700 | """Creates a new `IOCIProjectSeries`.""" |
701 | |
702 | |
703 | +class IOCIProjectLegitimate(Interface): |
704 | + """IOCIProject methods that require launchpad.AnyLegitimatePerson |
705 | + permission. |
706 | + """ |
707 | + @call_with(registrant=REQUEST_USER) |
708 | + @operation_parameters( |
709 | + name=TextLine( |
710 | + title=_("OCI Recipe name."), |
711 | + description=_("The name of the new OCI Recipe."), |
712 | + required=True), |
713 | + owner=PersonChoice( |
714 | + title=_("Person or team that owns the new OCI Recipe."), |
715 | + vocabulary="AllUserTeamsParticipationPlusSelf", |
716 | + required=True), |
717 | + git_ref=Reference(IGitRef, title=_("Git branch."), required=True), |
718 | + build_file=TextLine( |
719 | + title=_("Build file path."), |
720 | + description=_( |
721 | + "The relative path to the file within this recipe's " |
722 | + "branch that defines how to build the recipe."), |
723 | + constraint=path_does_not_escape, |
724 | + required=True), |
725 | + description=Text( |
726 | + title=_("Description for this recipe."), |
727 | + description=_("A short description of this recipe."), |
728 | + required=False), |
729 | + build_daily=Bool( |
730 | + title=_("Should this recipe be built daily?."), required=False)) |
731 | + @export_factory_operation(Interface, []) |
732 | + @operation_for_version("devel") |
733 | + def newRecipe(name, registrant, owner, git_ref, build_file, |
734 | + description=None, build_daily=False, |
735 | + require_virtualized=True): |
736 | + """Create an IOCIRecipe for this project.""" |
737 | + |
738 | + |
739 | class IOCIProject(IOCIProjectView, IOCIProjectEdit, |
740 | - IOCIProjectEditableAttributes): |
741 | + IOCIProjectEditableAttributes, IOCIProjectLegitimate): |
742 | """A project containing Open Container Initiative recipes.""" |
743 | |
744 | export_as_webservice_entry( |
745 | diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py |
746 | index aa3a322..2967007 100644 |
747 | --- a/lib/lp/registry/model/ociproject.py |
748 | +++ b/lib/lp/registry/model/ociproject.py |
749 | @@ -25,6 +25,7 @@ from zope.interface import implementer |
750 | from zope.security.proxy import removeSecurityProxy |
751 | |
752 | from lp.bugs.model.bugtarget import BugTargetBase |
753 | +from lp.oci.interfaces.ocirecipe import IOCIRecipeSet |
754 | from lp.registry.interfaces.distribution import IDistribution |
755 | from lp.registry.interfaces.ociproject import ( |
756 | IOCIProject, |
757 | @@ -107,6 +108,21 @@ class OCIProject(BugTargetBase, StormBase): |
758 | bugtargetname = display_name |
759 | bugtargetdisplayname = display_name |
760 | |
761 | + def newRecipe(self, name, registrant, owner, git_ref, |
762 | + build_file, description=None, build_daily=False, |
763 | + require_virtualized=True): |
764 | + return getUtility(IOCIRecipeSet).new( |
765 | + name=name, |
766 | + registrant=registrant, |
767 | + owner=owner, |
768 | + oci_project=self, |
769 | + git_ref=git_ref, |
770 | + build_file=build_file, |
771 | + description=description, |
772 | + require_virtualized=require_virtualized, |
773 | + build_daily=build_daily, |
774 | + ) |
775 | + |
776 | def newSeries(self, name, summary, registrant, |
777 | status=SeriesStatus.DEVELOPMENT, date_created=DEFAULT): |
778 | """See `IOCIProject`.""" |
779 | diff --git a/lib/lp/registry/tests/test_personmerge.py b/lib/lp/registry/tests/test_personmerge.py |
780 | index 890b421..02fefcc 100644 |
781 | --- a/lib/lp/registry/tests/test_personmerge.py |
782 | +++ b/lib/lp/registry/tests/test_personmerge.py |
783 | @@ -1,4 +1,4 @@ |
784 | -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
785 | +# Copyright 2009-2020 Canonical Ltd. This software is licensed under the |
786 | # GNU Affero General Public License version 3 (see the file LICENSE). |
787 | |
788 | """Tests for merge_people.""" |
789 | @@ -18,7 +18,10 @@ from zope.security.proxy import removeSecurityProxy |
790 | from lp.app.enums import InformationType |
791 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
792 | from lp.code.interfaces.gitrepository import IGitRepositorySet |
793 | -from lp.oci.interfaces.ocirecipe import IOCIRecipeSet |
794 | +from lp.oci.interfaces.ocirecipe import ( |
795 | + IOCIRecipeSet, |
796 | + OCI_RECIPE_ALLOW_CREATE, |
797 | + ) |
798 | from lp.registry.interfaces.accesspolicy import ( |
799 | IAccessArtifactGrantSource, |
800 | IAccessPolicyGrantSource, |
801 | @@ -664,6 +667,7 @@ class TestMergePeople(TestCaseWithFactory, KarmaTestMixin): |
802 | def test_merge_moves_oci_recipes(self): |
803 | # When person/teams are merged, oci recipes owned by the from |
804 | # person are moved. |
805 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
806 | duplicate = self.factory.makePerson() |
807 | mergee = self.factory.makePerson() |
808 | self.factory.makeOCIRecipe(registrant=duplicate, owner=duplicate) |
809 | @@ -676,6 +680,7 @@ class TestMergePeople(TestCaseWithFactory, KarmaTestMixin): |
810 | def test_merge_with_duplicated_oci_recipes(self): |
811 | # If both the from and to people have oci recipes with the same |
812 | # name, merging renames the duplicate from the from person's side. |
813 | + self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
814 | duplicate = self.factory.makePerson() |
815 | mergee = self.factory.makePerson() |
816 | [ref] = self.factory.makeGitRefs() |
817 | diff --git a/lib/lp/services/webhooks/tests/test_job.py b/lib/lp/services/webhooks/tests/test_job.py |
818 | index caf81ce..fde24da 100644 |
819 | --- a/lib/lp/services/webhooks/tests/test_job.py |
820 | +++ b/lib/lp/services/webhooks/tests/test_job.py |
821 | @@ -37,7 +37,10 @@ from zope.component import getUtility |
822 | from zope.security.proxy import removeSecurityProxy |
823 | |
824 | from lp.app import versioninfo |
825 | -from lp.oci.interfaces.ocirecipe import OCI_RECIPE_WEBHOOKS_FEATURE_FLAG |
826 | +from lp.oci.interfaces.ocirecipe import ( |
827 | + OCI_RECIPE_ALLOW_CREATE, |
828 | + OCI_RECIPE_WEBHOOKS_FEATURE_FLAG, |
829 | + ) |
830 | from lp.services.database.interfaces import IStore |
831 | from lp.services.features.testing import FeatureFixture |
832 | from lp.services.job.interfaces.job import JobStatus |
833 | @@ -358,7 +361,8 @@ class TestWebhookDeliveryJob(TestCaseWithFactory): |
834 | def test_oci_recipe__repr__(self): |
835 | # `WebhookDeliveryJob` objects for OCI recipes have an informative |
836 | # __repr__. |
837 | - with FeatureFixture({OCI_RECIPE_WEBHOOKS_FEATURE_FLAG: "on"}): |
838 | + with FeatureFixture({OCI_RECIPE_WEBHOOKS_FEATURE_FLAG: "on", |
839 | + OCI_RECIPE_ALLOW_CREATE: 'on'}): |
840 | recipe = self.factory.makeOCIRecipe() |
841 | hook = self.factory.makeWebhook(target=recipe) |
842 | job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'}) |
cjwatson, it took a while to adjust all the tests that needed to `OCIRecipe. __init_ _` or `OCIRecipeAddVi ew.initialize` , and those adjustments made the diff bigger, although it's just setting the correct feature flag where appropriate.
Anyway, I think it covers all requested changes. Let me know if you want to do another round of review.