Merge lp:~salgado/offspring/make-piston-handlers-privacy-aware into lp:offspring
- make-piston-handlers-privacy-aware
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Kevin McDermott |
Approved revision: | 110 |
Merged at revision: | 105 |
Proposed branch: | lp:~salgado/offspring/make-piston-handlers-privacy-aware |
Merge into: | lp:offspring |
Prerequisite: | lp:~salgado/offspring/builds-view |
Diff against target: |
726 lines (+467/-57) 9 files modified
config/offspring.cfg (+4/-4) lib/offspring/web/queuemanager/handlers.py (+35/-27) lib/offspring/web/queuemanager/models.py (+4/-2) lib/offspring/web/queuemanager/tests/factory.py (+3/-2) lib/offspring/web/queuemanager/tests/helpers.py (+13/-0) lib/offspring/web/queuemanager/tests/test_handlers.py (+404/-0) lib/offspring/web/queuemanager/tests/test_views.py (+4/-16) lib/offspring/web/settings.py (+0/-3) lib/offspring/web/settings_production.py (+0/-3) |
To merge this branch: | bzr merge lp:~salgado/offspring/make-piston-handlers-privacy-aware |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kevin McDermott | Approve | ||
Review via email: mp+84293@code.launchpad.net |
Commit message
Description of the change
Update the piston handlers to include private objects the user is allowed to see.
- 105. By Guilherme Salgado
-
Plenty of docstrings for tests
Kevin McDermott (bigkevmcd) wrote : | # |
#11
- return RC.NOT_FOUND
+ return rc.NOT_FOUND
There is no test for this change...
Guilherme Salgado (salgado) wrote : | # |
On Wed, 2011-12-14 at 14:12 +0000, Kevin McDermott wrote:
> Review: Needs Fixing
>
> Looks good with some small fixes...thanks for adding so many tests!
>
> === modified file 'lib/offspring/
>
> #1
>
> + project = projects.
> + build = BuildResult.
> + request.
> except:
> return rc.NOT_FOUND
>
> Please change this "bare except" to list the exceptions expected...there are couple of cases of this...
Ok, fixed all of them.
> #2
>
> +# TODO: Must be made privacy-aware.
>
> Isn't that what this branch does, or is there something missing, that's not captured in the comment?
This one is a bit more involved and this branch was already big enough,
so I did it in a separate one.
> #3
>
> + p = Project.
> + pk=projectName)
>
> s/p/project/
Fixed
>
> #4
>
> +from .test_handlers import *
>
> "Relative imports for intra-package imports are highly discouraged."
>
> In any case, merge from trunk, and you won't need to add the import, as nose will find your tests without the import (or run your tests twice if you add it ;-)
Oh, cool, got rid of that.
>
>
> === added file 'lib/offspring/
>
> #5
>
> +from django.
> + Group,
> + Permission,
> + )
>
> Will all fit on one line...
>
> #6
>
> +def grant_permissio
>
> No docstring...
Fixed both
>
> #7
>
> +def make_build(
>
> No docstring...but, it'd be nicer if this was at the top of the file,
> so that you know what it does when you're reading the tests that use
> it?
I prefer it at the bottom as I think one should not have to read the
implementation of helper methods to understand what a test does, and I
think in this case it's pretty clear what it does by its name alone.
>
> The docstring should be fixed, but moving it in the file is a personal
> preference...
Ok
>
> #8
>
> + """
> + Check that the builds of private projects are not visible to members
> + of the project's access group.
> + """
> + # If the user has no rights to see a given project, they'll get an
> + # empty list of builds, just like if the project didn't exist.
>
> Wouldn't the comment be better as part of the docstring?
Maybe; moved it there.
>
> #9
>
> + def assert_
>
> I appreciate that this might appear to go against my PEP8 requirement, but...
>
> "New modules and packages (including third party frameworks) should be written to these standards, but where an existing library has a different style, internal consistency is preferred."
>
> So, this would look nicer if it was assertBuildIsVi
>
> Which would make it clearer that it was intended to be an extension to TestCase's assertions :-)
>
> There are a few cases like this...
Makes sense; fixed them all
>
> #10
>
> assert_
>
> + def assert_
Guilherme Salgado (salgado) wrote : | # |
> #11
>
> - return RC.NOT_FOUND
> + return rc.NOT_FOUND
>
> There is no test for this change...
Indeed, but when I tried to write one I realized it's impossible to exercise this piece of code as the code in the try block is only filtering the results so it won't raise an exception if something is not found. I've removed the try/except around the code as the real protection against not found build requests is further down and that is tested.
- 106. By Guilherme Salgado
-
A few changes suggested by Kevin
- 107. By Guilherme Salgado
-
merge trunk
- 108. By Guilherme Salgado
-
A few more changes suggested by Kevin
- 109. By Guilherme Salgado
-
Remove base_dir from config/
offspring. cfg or else the tree root will not be used - 110. By Guilherme Salgado
-
Fix one last thing Keving mentioned
Kevin McDermott (bigkevmcd) wrote : | # |
Thanks for the fixes.
Preview Diff
1 | === modified file 'config/offspring.cfg' |
2 | --- config/offspring.cfg 2011-11-30 14:15:52 +0000 |
3 | +++ config/offspring.cfg 2011-12-14 16:08:38 +0000 |
4 | @@ -7,7 +7,8 @@ |
5 | #=============================================================================== |
6 | # Offspring Global Defaults |
7 | # |
8 | -# base_dir: Base directory of offspring install |
9 | +# base_dir: Base directory of offspring install. If ommitted, use the root of |
10 | +# the Offspring tree. |
11 | # bin_dir: Directory to offspring binaries |
12 | # config_dir: Directory to offspring configuration files |
13 | # admin_email: E-mail admin notifications should be sent to by default |
14 | @@ -21,7 +22,6 @@ |
15 | #=============================================================================== |
16 | |
17 | [DEFAULT] |
18 | -base_dir: /srv |
19 | bin_dir: %(base_dir)s/bin |
20 | config_dir: %(base_dir)s/config |
21 | log_dir: %(base_dir)s/logs |
22 | @@ -159,8 +159,8 @@ |
23 | |
24 | [publisher] |
25 | db: postgres://offspring:temp1234@localhost/offspring |
26 | -development_pool: /srv/builds/ |
27 | -production_pool: /srv/partners/ |
28 | +development_pool: %(base_dir)s/builds/ |
29 | +production_pool: %(base_dir)s/partners/ |
30 | signer_directory: |
31 | signer_files: |
32 | signer_results: |
33 | |
34 | === modified file 'lib/offspring/web/queuemanager/handlers.py' |
35 | --- lib/offspring/web/queuemanager/handlers.py 2011-08-13 07:24:06 +0000 |
36 | +++ lib/offspring/web/queuemanager/handlers.py 2011-12-14 16:08:38 +0000 |
37 | @@ -43,10 +43,12 @@ |
38 | allowed_methods = ('GET') |
39 | |
40 | def read(self, request, projectName, buildName): |
41 | + projects = Project.all_objects.accessible_by_user(request.user) |
42 | try: |
43 | - project = Project.objects.get(pk=projectName) |
44 | - build = BuildResult.objects.get(project=project, name=buildName) |
45 | - except: |
46 | + project = projects.get(pk=projectName) |
47 | + build = BuildResult.all_objects.accessible_by_user( |
48 | + request.user).get(project=project, name=buildName) |
49 | + except (BuildResult.DoesNotExist, Project.DoesNotExist): |
50 | return rc.NOT_FOUND |
51 | manifestPath = glob.glob(build.result_directory + '/images/*/*.manifest')[0] |
52 | return [ {'package' : package, 'version' : version} for package, version in BuildManifest(manifestPath).packages.iteritems() ] |
53 | @@ -56,15 +58,18 @@ |
54 | allowed_methods = ('GET') |
55 | |
56 | def read(self, request, projectName, buildName): |
57 | + projects = Project.all_objects.accessible_by_user(request.user) |
58 | + builds = BuildResult.all_objects.accessible_by_user(request.user) |
59 | try: |
60 | - project = Project.objects.get(pk=projectName) |
61 | - base_build = BuildResult.objects.get(project=project, name=buildName) |
62 | - except: |
63 | + project = projects.get(pk=projectName) |
64 | + base_build = builds.get(project=project, name=buildName) |
65 | + except (BuildResult.DoesNotExist, Project.DoesNotExist): |
66 | return rc.NOT_FOUND |
67 | if request.GET.has_key('build'): |
68 | try: |
69 | - target_build = BuildResult.objects.get(project=project, name=request.GET['build']) |
70 | - except: |
71 | + target_build = builds.get( |
72 | + project=project, name=request.GET['build']) |
73 | + except BuildResult.DoesNotExist: |
74 | return rc.NOT_FOUND |
75 | else: |
76 | return rc.BAD_REQUEST |
77 | @@ -81,6 +86,7 @@ |
78 | return output |
79 | |
80 | |
81 | +# TODO: Must be made privacy-aware. |
82 | class ProjectNotificationSubscriptionHandler(BaseHandler): |
83 | allowed_methods = ('GET', 'POST', 'DELETE') |
84 | model = ProjectNotificationSubscription |
85 | @@ -138,7 +144,7 @@ |
86 | 'reason', 'result', 'notes', 'is_released') |
87 | |
88 | def read(self, request, projectName=None, buildName=None, builderName=None): |
89 | - results = BuildResult.objects.all() |
90 | + results = BuildResult.all_objects.accessible_by_user(request.user) |
91 | if projectName is not None: |
92 | results = results.filter(project__name=projectName) |
93 | if buildName is not None: |
94 | @@ -155,11 +161,11 @@ |
95 | |
96 | def read(self, request, projectName=None, request_id=None): |
97 | try: |
98 | + requests = BuildRequest.all_objects.accessible_by_user( |
99 | + request.user) |
100 | if projectName is not None: |
101 | - requests = BuildRequest.objects.filter(project__name=projectName) |
102 | - else: |
103 | - requests = BuildRequest.objects |
104 | - except: |
105 | + requests = requests.filter(project__name=projectName) |
106 | + except BuildRequest.DoesNotExist: |
107 | return rc.NOT_FOUND |
108 | if request_id is not None: |
109 | return requests.get(pk=request_id) |
110 | @@ -169,18 +175,15 @@ |
111 | @method_decorator(login_required) |
112 | def delete(self, request, projectName=None, request_id=None): |
113 | if request.user.has_perm("queuemanager.can_build"): |
114 | - try: |
115 | - if projectName is not None: |
116 | - requests = BuildRequest.objects.filter(project__name=projectName) |
117 | - else: |
118 | - requests = BuildRequest.objects |
119 | - except: |
120 | - return RC.NOT_FOUND |
121 | + requests = BuildRequest.all_objects.accessible_by_user( |
122 | + request.user) |
123 | + if projectName is not None: |
124 | + requests = requests.filter(project__name=projectName) |
125 | if request_id is not None: |
126 | try: |
127 | requests.get(pk=request_id).delete() |
128 | return rc.DELETED |
129 | - except: |
130 | + except BuildRequest.DoesNotExist: |
131 | return rc.NOT_FOUND |
132 | else: |
133 | try: |
134 | @@ -195,14 +198,18 @@ |
135 | def create(self, request, projectName): |
136 | if request.user.has_perm("queuemanager.can_build"): |
137 | try: |
138 | - p = Project.objects.get(pk=projectName) |
139 | + project = Project.all_objects.accessible_by_user( |
140 | + request.user).get(pk=projectName) |
141 | if request.POST.has_key('build_reason'): |
142 | - request = BuildRequest.queue_build(p, request.user, reason=request.POST['build_reason'], scoreBonus=25) |
143 | + reason = request.POST['build_reason'] |
144 | + request = BuildRequest.queue_build( |
145 | + project, request.user, reason=reason, scoreBonus=25) |
146 | else: |
147 | - request = BuildRequest.queue_build(p, request.user, scoreBonus=25) |
148 | + request = BuildRequest.queue_build( |
149 | + project, request.user, scoreBonus=25) |
150 | request.save() |
151 | return rc.CREATED |
152 | - except: |
153 | + except (Project.DoesNotExist, BuildRequest.DoesNotExist): |
154 | return rc.BAD_REQUEST |
155 | else: |
156 | return rc.FORBIDDEN |
157 | @@ -217,8 +224,9 @@ |
158 | 'created_at', 'published_at', 'status', 'checklist_url', 'notes') |
159 | |
160 | def read(self, request, projectName): |
161 | + objects = Release.all_objects.accessible_by_user(request.user) |
162 | try: |
163 | - releases = Release.objects.filter(build__project__name=projectName) |
164 | - except: |
165 | + releases = objects.filter(build__project__name=projectName) |
166 | + except Release.DoesNotExist: |
167 | return rc.NOT_FOUND |
168 | return releases |
169 | |
170 | === modified file 'lib/offspring/web/queuemanager/models.py' |
171 | --- lib/offspring/web/queuemanager/models.py 2011-12-13 08:53:34 +0000 |
172 | +++ lib/offspring/web/queuemanager/models.py 2011-12-14 16:08:38 +0000 |
173 | @@ -13,7 +13,6 @@ |
174 | ) |
175 | import math |
176 | |
177 | -from django.conf import settings |
178 | from django.contrib.auth.models import AnonymousUser, User |
179 | from django.db import ( |
180 | connection, |
181 | @@ -24,9 +23,12 @@ |
182 | AccessGroup, |
183 | AccessGroupMixin, |
184 | AccessManager) |
185 | +from offspring.config import get_configuration |
186 | from offspring.enums import ProjectBuildStates |
187 | |
188 | |
189 | +config = get_configuration() |
190 | + |
191 | ARCH_CHOICES = ( |
192 | (u'i386', u'i386'), |
193 | (u'amd64', u'amd64'), |
194 | @@ -422,7 +424,7 @@ |
195 | def result_directory(self): |
196 | if self.name: |
197 | try: |
198 | - base_dir = getattr(settings, 'BUILDRESULTS_DIRECTORY', '/srv/builds/') |
199 | + base_dir = config.get('builder', 'result_dir') |
200 | buildDate, buildId = self.name.rsplit('-', 1) |
201 | except: |
202 | return None |
203 | |
204 | === modified file 'lib/offspring/web/queuemanager/tests/factory.py' |
205 | --- lib/offspring/web/queuemanager/tests/factory.py 2011-12-02 13:55:56 +0000 |
206 | +++ lib/offspring/web/queuemanager/tests/factory.py 2011-12-14 16:08:38 +0000 |
207 | @@ -73,7 +73,8 @@ |
208 | title = self.get_unique_string() |
209 | return ProjectGroup.objects.create(name=name, title=title) |
210 | |
211 | - def make_build_result(self, project=None, name=None, result=None): |
212 | + def make_build_result(self, project=None, name=None, result=None, |
213 | + builder=None): |
214 | """ |
215 | Create a BuildResult with the given project or a newly created one if |
216 | none is given. |
217 | @@ -83,7 +84,7 @@ |
218 | if project is None: |
219 | project = self.make_project() |
220 | return BuildResult.objects.create( |
221 | - project=project, name=name, result=result) |
222 | + project=project, name=name, result=result, builder=builder) |
223 | |
224 | def make_release(self, build=None, name=None, creator=None): |
225 | """ |
226 | |
227 | === added file 'lib/offspring/web/queuemanager/tests/helpers.py' |
228 | --- lib/offspring/web/queuemanager/tests/helpers.py 1970-01-01 00:00:00 +0000 |
229 | +++ lib/offspring/web/queuemanager/tests/helpers.py 2011-12-14 16:08:38 +0000 |
230 | @@ -0,0 +1,13 @@ |
231 | +from django.contrib.auth.models import Group, Permission |
232 | + |
233 | + |
234 | +def grant_permission_to_user(user, permission_codename): |
235 | + """Grant the permission with the given codename to the given user.""" |
236 | + group = Group(name=user.username) |
237 | + group.save() |
238 | + group.permissions.add( |
239 | + Permission.objects.get(codename=permission_codename)) |
240 | + group.save() |
241 | + user.groups.add(group) |
242 | + user.save() |
243 | + return user |
244 | |
245 | === added file 'lib/offspring/web/queuemanager/tests/test_handlers.py' |
246 | --- lib/offspring/web/queuemanager/tests/test_handlers.py 1970-01-01 00:00:00 +0000 |
247 | +++ lib/offspring/web/queuemanager/tests/test_handlers.py 2011-12-14 16:08:38 +0000 |
248 | @@ -0,0 +1,404 @@ |
249 | +import errno |
250 | +import os |
251 | + |
252 | +from django.contrib.auth import authenticate, login |
253 | +from django.http import HttpRequest, HttpResponse |
254 | +from django.test import TestCase |
255 | +from django.contrib.sessions.middleware import SessionMiddleware |
256 | + |
257 | +from piston.utils import rc |
258 | + |
259 | +from offspring.web.queuemanager.models import BuildRequest |
260 | +from offspring.web.queuemanager.handlers import ( |
261 | + BuildManifestComparisonHandler, |
262 | + BuildManifestHandler, |
263 | + BuildRequestHandler, |
264 | + BuildResultHandler, |
265 | + ReleaseHandler, |
266 | + ) |
267 | +from offspring.web.queuemanager.tests.factory import factory |
268 | +from offspring.web.queuemanager.tests.helpers import grant_permission_to_user |
269 | + |
270 | + |
271 | +class BaseHandlerTestCase(TestCase): |
272 | + """ |
273 | + A TestCase with some custom setup and helpers for testing Piston handlers. |
274 | + """ |
275 | + |
276 | + def setUp(self): |
277 | + super(BaseHandlerTestCase, self).setUp() |
278 | + self.request = HttpRequest() |
279 | + self.request.method = 'GET' |
280 | + # Set request.user and process the request manually using |
281 | + # SessionMiddleware so that we can use login(request, user) in tests. |
282 | + self.request.user = None |
283 | + SessionMiddleware().process_request(self.request) |
284 | + |
285 | + def _login(self, user): |
286 | + user = authenticate(username=user.username, password=user.username) |
287 | + login(self.request, user) |
288 | + return user |
289 | + |
290 | + |
291 | +class BuildResultHandlerTests(BaseHandlerTestCase): |
292 | + |
293 | + def test_builds_for_inexistent_project(self): |
294 | + """ |
295 | + Check that no builds are returned if there's no project with the given |
296 | + name. |
297 | + """ |
298 | + results = BuildResultHandler().read( |
299 | + self.request, projectName='not-existent') |
300 | + self.assertEqual(0, results.count()) |
301 | + |
302 | + def test_inexistent_build(self): |
303 | + """ |
304 | + Check that no builds are returned if there's no build with the given |
305 | + name. |
306 | + """ |
307 | + results = BuildResultHandler().read( |
308 | + self.request, buildName='not-existent') |
309 | + self.assertEqual(0, results.count()) |
310 | + |
311 | + def test_public_project_builds(self): |
312 | + """ |
313 | + Check that the builds of public projects are visible to anyone. |
314 | + """ |
315 | + build = make_build(is_private=False) |
316 | + self.assertBuildIsVisibleToUser(build, factory.make_user()) |
317 | + |
318 | + def test_private_project_build_is_visible_to_project_owner(self): |
319 | + """ |
320 | + Check that the builds of private projects are visible to the project's |
321 | + owner. |
322 | + """ |
323 | + build = make_build(is_private=True) |
324 | + self.assertBuildIsVisibleToUser(build, build.project.owner) |
325 | + |
326 | + def test_private_project_builds_invisible_to_user(self): |
327 | + """ |
328 | + Check that the builds of private projects are not visible to members |
329 | + of the project's access group. |
330 | + |
331 | + If the user has no rights to see a given project, they'll get an |
332 | + empty list of builds, just like if the project didn't exist. |
333 | + """ |
334 | + build = make_build(is_private=True) |
335 | + self.assertBuildIsNotVisibleToUser(build, factory.make_user()) |
336 | + |
337 | + def assertBuildIsVisibleToUser(self, build, user): |
338 | + """ |
339 | + Assert that the given build is visible to the given user. |
340 | + """ |
341 | + user = self._login(user) |
342 | + handler = BuildResultHandler() |
343 | + |
344 | + # It is visible when looking at all project builds... |
345 | + results = handler.read(self.request, projectName=build.project.name) |
346 | + self.assertEqual(1, results.count()) |
347 | + self.assertEqual([build], list(results)) |
348 | + |
349 | + # when looking at one specific build ... |
350 | + results = handler.read(self.request, buildName=build.name) |
351 | + self.assertEqual(1, results.count()) |
352 | + self.assertEqual([build], list(results)) |
353 | + |
354 | + # and when looking at all builds of a builder. |
355 | + results = handler.read(self.request, builderName=build.builder.name) |
356 | + self.assertEqual(1, results.count()) |
357 | + self.assertEqual([build], list(results)) |
358 | + |
359 | + def assertBuildIsNotVisibleToUser(self, build, user): |
360 | + """ |
361 | + Assert that the given build is not visible to the given user. |
362 | + """ |
363 | + user = self._login(user) |
364 | + handler = BuildResultHandler() |
365 | + |
366 | + # It is *not* visible when looking at all project builds... |
367 | + results = handler.read(self.request, projectName=build.project.name) |
368 | + self.assertEqual(0, results.count()) |
369 | + |
370 | + # when looking at one specific build ... |
371 | + results = handler.read(self.request, buildName=build.name) |
372 | + self.assertEqual(0, results.count()) |
373 | + |
374 | + # and when looking at all builds of a builder. |
375 | + results = handler.read(self.request, builderName=build.builder.name) |
376 | + self.assertEqual(0, results.count()) |
377 | + |
378 | + |
379 | +class BuildRequestHandlerTests(BaseHandlerTestCase): |
380 | + |
381 | + def test_public_build_request_is_visible_to_anyone(self): |
382 | + """ |
383 | + Check that build requests of public projects are visible to anyone. |
384 | + """ |
385 | + job = factory.make_build_request( |
386 | + project=factory.make_project(is_private=False)) |
387 | + results = BuildRequestHandler().read( |
388 | + self.request, projectName=job.project.name, request_id=job.id) |
389 | + self.assertEqual(job, results) |
390 | + |
391 | + def test_private_build_request_is_visible_to_owner(self): |
392 | + """ |
393 | + Check that build requests of private projects are visible to the |
394 | + project's owner. |
395 | + """ |
396 | + job = factory.make_build_request( |
397 | + project=factory.make_project(is_private=True)) |
398 | + self._login(job.project.owner) |
399 | + results = BuildRequestHandler().read( |
400 | + self.request, projectName=job.project.name, request_id=job.id) |
401 | + self.assertEqual(job, results) |
402 | + |
403 | + def test_private_build_request_is_not_visible_to_others(self): |
404 | + """ |
405 | + Check that build requests of private projects are not visible if the |
406 | + user doesn't have the rights to see the project. |
407 | + """ |
408 | + job = factory.make_build_request( |
409 | + project=factory.make_project(is_private=True)) |
410 | + self._login(factory.make_user()) |
411 | + self.assertRaises( |
412 | + BuildRequest.DoesNotExist, |
413 | + BuildRequestHandler().read, |
414 | + self.request, projectName=job.project.name, request_id=job.id) |
415 | + |
416 | + def test_new_build_request_on_public_project(self): |
417 | + """ |
418 | + Check that anybody can request a build of a public project. |
419 | + """ |
420 | + project = factory.make_project(is_private=False) |
421 | + self.assertBuildHandlerCreation( |
422 | + project, factory.make_user(), rc.CREATED.status_code) |
423 | + |
424 | + def test_owner_requests_new_build_on_private_project(self): |
425 | + """ |
426 | + Check that the project owner can request the build of a private |
427 | + project. |
428 | + """ |
429 | + project = factory.make_project(is_private=True) |
430 | + self.assertBuildHandlerCreation( |
431 | + project, project.owner, rc.CREATED.status_code) |
432 | + |
433 | + def test_other_user_requests_new_build_on_private_project(self): |
434 | + """ |
435 | + Check that users who can't see a project cannot request a build |
436 | + of it. |
437 | + """ |
438 | + project = factory.make_project(is_private=True) |
439 | + self.assertBuildHandlerCreation( |
440 | + project, factory.make_user(), rc.BAD_REQUEST.status_code) |
441 | + |
442 | + def test_delete_build_request_on_public_project(self): |
443 | + """ |
444 | + Check that anybody can cancel a build request of a public project. |
445 | + """ |
446 | + job = factory.make_build_request( |
447 | + project=factory.make_project(is_private=False)) |
448 | + self.assertBuildHandlerDeletion( |
449 | + job, factory.make_user(), rc.DELETED.status_code) |
450 | + |
451 | + def test_owner_deletes_build_request_on_private_project(self): |
452 | + """ |
453 | + Check that the project owner can cancel a build request of a private |
454 | + project. |
455 | + """ |
456 | + job = factory.make_build_request( |
457 | + project=factory.make_project(is_private=True)) |
458 | + self.assertBuildHandlerDeletion( |
459 | + job, job.project.owner, rc.DELETED.status_code) |
460 | + |
461 | + def test_other_user_deletes_build_request_on_private_project(self): |
462 | + """ |
463 | + Check that users who can't see a project cannot cancel a build |
464 | + request of it. |
465 | + """ |
466 | + job = factory.make_build_request( |
467 | + project=factory.make_project(is_private=True)) |
468 | + self.assertBuildHandlerDeletion( |
469 | + job, factory.make_user(), rc.NOT_FOUND.status_code) |
470 | + |
471 | + def assertBuildHandlerCreation(self, project, user, return_code): |
472 | + """ |
473 | + Assert that BuildRequestHandler.create() returns the given code. |
474 | + |
475 | + Also grant the given user the 'can_build' permission and logs it in. |
476 | + """ |
477 | + grant_permission_to_user(user, 'can_build') |
478 | + self._login(user) |
479 | + result = BuildRequestHandler().create( |
480 | + self.request, projectName=project.name) |
481 | + self.assertEqual(return_code, result.status_code) |
482 | + |
483 | + def assertBuildHandlerDeletion(self, job, user, return_code): |
484 | + """ |
485 | + Assert that BuildRequestHandler.delete() returns the given code. |
486 | + |
487 | + Also grant the given user the 'can_build' permission and logs it in. |
488 | + """ |
489 | + grant_permission_to_user(user, 'can_build') |
490 | + self._login(user) |
491 | + result = BuildRequestHandler().delete( |
492 | + self.request, projectName=job.project.name, request_id=job.id) |
493 | + self.assertEqual(return_code, result.status_code) |
494 | + |
495 | + |
496 | +class ReleaseHandlerTests(BaseHandlerTestCase): |
497 | + |
498 | + def test_public_project_release(self): |
499 | + """ |
500 | + Check that releases of public projects are visible to anyone. |
501 | + """ |
502 | + release = factory.make_release(make_build(is_private=False)) |
503 | + results = ReleaseHandler().read( |
504 | + self.request, projectName=release.build.project.name) |
505 | + self.assertEqual(1, results.count()) |
506 | + self.assertEqual([release], list(results)) |
507 | + |
508 | + def test_private_project_release_is_visible_to_owner(self): |
509 | + """ |
510 | + Check that releases of private projects are visible to the project's |
511 | + owner. |
512 | + """ |
513 | + release = factory.make_release(make_build(is_private=True)) |
514 | + self._login(release.build.project.owner) |
515 | + results = ReleaseHandler().read( |
516 | + self.request, projectName=release.build.project.name) |
517 | + self.assertEqual(1, results.count()) |
518 | + self.assertEqual([release], list(results)) |
519 | + |
520 | + def test_private_project_release_is_not_visible_to_others(self): |
521 | + """ |
522 | + Check that releases of private projects are not visible to users who |
523 | + don't have the rights to see the project. |
524 | + """ |
525 | + release = factory.make_release(make_build(is_private=True)) |
526 | + user = self._login(factory.make_user()) |
527 | + self.assertFalse(release.is_visible_to(user)) |
528 | + results = ReleaseHandler().read( |
529 | + self.request, projectName=release.build.project.name) |
530 | + self.assertEqual(0, results.count()) |
531 | + |
532 | + |
533 | +class BuildManifestHandlerTests(BaseHandlerTestCase): |
534 | + |
535 | + def test_public_project_build_manifest(self): |
536 | + """ |
537 | + Check that build manifests of public projects are visible to anyone. |
538 | + """ |
539 | + build = make_build(is_private=False) |
540 | + make_build_manifest_file(build) |
541 | + results = BuildManifestHandler().read( |
542 | + self.request, build.project.name, build.name) |
543 | + self.assertEqual([{'package': 'foo', 'version': '1.2'}], results) |
544 | + |
545 | + def test_private_project_build_manifest_is_visible_to_owner(self): |
546 | + """ |
547 | + Check that build manifests of private projects are visible to the |
548 | + project's owner. |
549 | + """ |
550 | + build = make_build(is_private=True) |
551 | + make_build_manifest_file(build) |
552 | + self._login(build.project.owner) |
553 | + results = BuildManifestHandler().read( |
554 | + self.request, build.project.name, build.name) |
555 | + self.assertEqual([{'package': 'foo', 'version': '1.2'}], results) |
556 | + |
557 | + def test_private_project_build_manifest_returns_404_for_others(self): |
558 | + """ |
559 | + Check that build manifests of private projects are not visible to |
560 | + users who don't have the rights to see the project. |
561 | + """ |
562 | + build = make_build(is_private=True) |
563 | + make_build_manifest_file(build) |
564 | + user = self._login(factory.make_user()) |
565 | + self.assertFalse(build.project.is_visible_to(user)) |
566 | + results = BuildManifestHandler().read( |
567 | + self.request, build.project.name, build.name) |
568 | + self.assertTrue(isinstance(results, HttpResponse)) |
569 | + self.assertEqual(404, results.status_code) |
570 | + |
571 | + |
572 | +class BuildManifestComparisonHandlerTests(BaseHandlerTestCase): |
573 | + |
574 | + def test_public_projects_build_manifest_comparison(self): |
575 | + """ |
576 | + Check that anyone can get a build comparison between builds of a |
577 | + public project. |
578 | + """ |
579 | + build = make_build(is_private=False) |
580 | + build2 = factory.make_build_result( |
581 | + project=build.project, builder=factory.make_lexbuilder()) |
582 | + make_build_manifest_file(build) |
583 | + make_build_manifest_file(build2) |
584 | + self.request.GET['build'] = build2.name |
585 | + results = BuildManifestComparisonHandler().read( |
586 | + self.request, build.project.name, build.name) |
587 | + self.assertEqual( |
588 | + [{'changeType': 'no_change', 'target_version': '1.2', |
589 | + 'base_version': '1.2', 'package': 'foo'}], |
590 | + results) |
591 | + |
592 | + def test_private_build_manifest_comparison_is_visible_to_owner(self): |
593 | + """ |
594 | + Check that the project owner can get a build comparison between builds |
595 | + of a private project. |
596 | + """ |
597 | + build = make_build(is_private=True) |
598 | + build2 = factory.make_build_result( |
599 | + project=build.project, builder=factory.make_lexbuilder()) |
600 | + make_build_manifest_file(build) |
601 | + make_build_manifest_file(build2) |
602 | + self._login(build.project.owner) |
603 | + self.request.GET['build'] = build2.name |
604 | + results = BuildManifestComparisonHandler().read( |
605 | + self.request, build.project.name, build.name) |
606 | + self.assertEqual( |
607 | + [{'changeType': 'no_change', 'target_version': '1.2', |
608 | + 'base_version': '1.2', 'package': 'foo'}], |
609 | + results) |
610 | + |
611 | + def test_private_build_manifest_comparison_returns_404_for_others(self): |
612 | + """ |
613 | + Check that those who don't have the rights to see a private project |
614 | + cannot get a build comparison between builds of that project. |
615 | + """ |
616 | + build = make_build(is_private=True) |
617 | + build2 = factory.make_build_result( |
618 | + project=build.project, builder=factory.make_lexbuilder()) |
619 | + make_build_manifest_file(build) |
620 | + make_build_manifest_file(build2) |
621 | + self._login(factory.make_user()) |
622 | + self.request.GET['build'] = build2.name |
623 | + results = BuildManifestComparisonHandler().read( |
624 | + self.request, build.project.name, build.name) |
625 | + self.assertTrue(isinstance(results, HttpResponse)) |
626 | + self.assertEqual(404, results.status_code) |
627 | + |
628 | + |
629 | +def make_build(is_private): |
630 | + """Create a new BuildResult associated to a newly created project. |
631 | + |
632 | + If is_private is True, the project is marked as private, otherwise it is |
633 | + marked as public. |
634 | + """ |
635 | + return factory.make_build_result( |
636 | + project=factory.make_project(is_private=is_private), |
637 | + builder=factory.make_lexbuilder()) |
638 | + |
639 | + |
640 | +def make_build_manifest_file(build): |
641 | + """ |
642 | + Create a manifest file where BuildManifestHandler expects to find it. |
643 | + """ |
644 | + manifest_dir = os.path.join(build.result_directory, 'images', 'build') |
645 | + try: |
646 | + os.makedirs(manifest_dir) |
647 | + except OSError, exc: |
648 | + if exc.errno != errno.EEXIST: |
649 | + raise |
650 | + path = os.path.join(manifest_dir, 'build.manifest') |
651 | + with open(path, 'w') as fd: |
652 | + fd.write('foo 1.2') |
653 | |
654 | === modified file 'lib/offspring/web/queuemanager/tests/test_views.py' |
655 | --- lib/offspring/web/queuemanager/tests/test_views.py 2011-12-14 12:13:02 +0000 |
656 | +++ lib/offspring/web/queuemanager/tests/test_views.py 2011-12-14 16:08:38 +0000 |
657 | @@ -2,8 +2,7 @@ |
658 | # GNU Affero General Public License version 3 (see the file LICENSE). |
659 | import urllib |
660 | |
661 | -from django.contrib.auth.models import AnonymousUser, Group, Permission |
662 | -from django.contrib.contenttypes.models import ContentType |
663 | +from django.contrib.auth.models import AnonymousUser |
664 | from django.core.urlresolvers import reverse |
665 | from django.http import Http404 |
666 | from django.test import TestCase |
667 | @@ -15,6 +14,7 @@ |
668 | from offspring.web.queuemanager.forms import LaunchpadProjectForm |
669 | |
670 | from offspring.web.queuemanager.tests.factory import factory |
671 | +from offspring.web.queuemanager.tests.helpers import grant_permission_to_user |
672 | |
673 | |
674 | class TestGetPossiblyPrivateObject(TestCase): |
675 | @@ -417,18 +417,6 @@ |
676 | return client.login(username=user.username, password=user.username) |
677 | |
678 | |
679 | -def grant_permission_to_user(user, permission_codename): |
680 | - """Grant the permission with the given codename to the given user.""" |
681 | - group = Group(name=user.username) |
682 | - group.save() |
683 | - group.permissions.add( |
684 | - Permission.objects.get(codename=permission_codename)) |
685 | - group.save() |
686 | - user.groups.add(group) |
687 | - user.save() |
688 | - return user |
689 | - |
690 | - |
691 | class LaunchpadProjectViewsTests(TestCase): |
692 | |
693 | def setUp(self): |
694 | @@ -451,8 +439,8 @@ |
695 | |
696 | def test_get_launchpad_project_add_url_gets_correct_form(self): |
697 | """ |
698 | - If the logged in user has the correct permissions, a GET request should |
699 | - get the LaunchpadProjectForm. |
700 | + If the logged in user has the correct permissions, a GET request |
701 | + should get the LaunchpadProjectForm. |
702 | """ |
703 | grant_permission_to_user(self.user, "add_launchpadproject") |
704 | self.client.login(username=self.user.username, |
705 | |
706 | === modified file 'lib/offspring/web/settings.py' |
707 | --- lib/offspring/web/settings.py 2011-11-18 15:45:06 +0000 |
708 | +++ lib/offspring/web/settings.py 2011-12-14 16:08:38 +0000 |
709 | @@ -131,6 +131,3 @@ |
710 | CELERY_RESULT_BACKEND = 'djcelery.backends.database.DatabaseBackend' |
711 | CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' |
712 | djcelery.setup_loader() |
713 | - |
714 | -# Path to build results |
715 | -BUILDRESULTS_DIRECTORY = '/srv/builds/' |
716 | |
717 | === modified file 'lib/offspring/web/settings_production.py' |
718 | --- lib/offspring/web/settings_production.py 2011-11-30 14:15:52 +0000 |
719 | +++ lib/offspring/web/settings_production.py 2011-12-14 16:08:38 +0000 |
720 | @@ -16,6 +16,3 @@ |
721 | EMAIL_HOST = 'localhost' |
722 | |
723 | MEDIA_URL = 'https://offspring.com/assets/' |
724 | - |
725 | -# Path to build results |
726 | -BUILDRESULTS_DIRECTORY = '/srv/offspring.com/www/builds/' |
Looks good with some small fixes...thanks for adding so many tests!
=== modified file 'lib/offspring/ web/queuemanage r/handlers. py'
#1
+ project = projects. get(pk= projectName) all_objects. accessible_ by_user( user).get( project= project, name=buildName)
+ build = BuildResult.
+ request.
except:
return rc.NOT_FOUND
Please change this "bare except" to list the exceptions expected...there are couple of cases of this...
#2
+# TODO: Must be made privacy-aware.
Isn't that what this branch does, or is there something missing, that's not captured in the comment?
#3
+ p = Project. all_objects. accessible_ by_user( request. user).get(
+ pk=projectName)
s/p/project/
#4
+from .test_handlers import *
"Relative imports for intra-package imports are highly discouraged."
In any case, merge from trunk, and you won't need to add the import, as nose will find your tests without the import (or run your tests twice if you add it ;-)
=== added file 'lib/offspring/ web/queuemanage r/tests/ helpers. py'
#5
+from django. contrib. auth.models import (
+ Group,
+ Permission,
+ )
Will all fit on one line...
#6
+def grant_permissio n_to_user( user, permission):
No docstring...
#7
+def make_build( is_private) :
No docstring...but, it'd be nicer if this was at the top of the file, so that you know what it does when you're reading the tests that use it?
The docstring should be fixed, but moving it in the file is a personal preference...
#8
+ """
+ Check that the builds of private projects are not visible to members
+ of the project's access group.
+ """
+ # If the user has no rights to see a given project, they'll get an
+ # empty list of builds, just like if the project didn't exist.
Wouldn't the comment be better as part of the docstring?
#9
+ def assert_ build_is_ visible_ to_user( self, build, user):
I appreciate that this might appear to go against my PEP8 requirement, but...
"New modules and packages (including third party frameworks) should be written to these standards, but where an existing library has a different style, internal consistency is preferred."
So, this would look nicer if it was assertBuildIsVi sibleToUser
Which would make it clearer that it was intended to be an extension to TestCase's assertions :-)
There are a few cases like this...
#10
assert_ create_ return_ code
+ def assert_ create_ return_ code(self, project, user, return_code):
How about:
+ def assertBuildHand lerCreation( self, project, user, return_code):
?
There are a couple of trivial conflicts with trunk...