Skip to content

Commit bf0495b

Browse files
nlhkabudi
authored andcommitted
Individual release view (#2879)
* Stub out individual release view * Include lazy files in view * Begin styling release detail page * Fix routes * Fix linting errors * Hook up delete release modal * Add tests * Use underscores in param names * Individual file deletion * Make files table responsive * Make delete error messages more explicit * Close modal when deleting release/file * Show checksums on release management page * Link to public release page * Fix linting errors * Fix a typo * Put digests in code block * Delete some dead code * Use 'hash' instead of 'checksum'
1 parent 595a318 commit bf0495b

File tree

16 files changed

+940
-20
lines changed

16 files changed

+940
-20
lines changed

tests/unit/manage/test_views.py

+291
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,297 @@ def test_manage_project_releases(self):
208208
}
209209

210210

211+
class TestManageProjectRelease:
212+
213+
def test_manage_project_release(self):
214+
files = pretend.stub()
215+
project = pretend.stub()
216+
release = pretend.stub(
217+
project=project,
218+
files=pretend.stub(all=lambda: files),
219+
)
220+
request = pretend.stub()
221+
view = views.ManageProjectRelease(release, request)
222+
223+
assert view.manage_project_release() == {
224+
'project': project,
225+
'release': release,
226+
'files': files,
227+
}
228+
229+
def test_delete_project_release(self, monkeypatch):
230+
release = pretend.stub(
231+
version='1.2.3',
232+
project=pretend.stub(name='foobar'),
233+
)
234+
request = pretend.stub(
235+
POST={'confirm_version': release.version},
236+
method="POST",
237+
db=pretend.stub(
238+
delete=pretend.call_recorder(lambda a: None),
239+
add=pretend.call_recorder(lambda a: None),
240+
),
241+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
242+
session=pretend.stub(
243+
flash=pretend.call_recorder(lambda *a, **kw: None)
244+
),
245+
user=pretend.stub(),
246+
remote_addr=pretend.stub(),
247+
)
248+
journal_obj = pretend.stub()
249+
journal_cls = pretend.call_recorder(lambda **kw: journal_obj)
250+
monkeypatch.setattr(views, 'JournalEntry', journal_cls)
251+
252+
view = views.ManageProjectRelease(release, request)
253+
254+
result = view.delete_project_release()
255+
256+
assert isinstance(result, HTTPSeeOther)
257+
assert result.headers["Location"] == "/the-redirect"
258+
259+
assert request.db.delete.calls == [pretend.call(release)]
260+
assert request.db.add.calls == [pretend.call(journal_obj)]
261+
assert journal_cls.calls == [
262+
pretend.call(
263+
name=release.project.name,
264+
action="remove",
265+
version=release.version,
266+
submitted_by=request.user,
267+
submitted_from=request.remote_addr,
268+
),
269+
]
270+
assert request.session.flash.calls == [
271+
pretend.call(
272+
f"Successfully deleted release {release.version!r}.",
273+
queue="success",
274+
)
275+
]
276+
assert request.route_path.calls == [
277+
pretend.call(
278+
'manage.project.releases',
279+
project_name=release.project.name,
280+
)
281+
]
282+
283+
def test_delete_project_release_no_confirm(self):
284+
release = pretend.stub(
285+
version='1.2.3',
286+
project=pretend.stub(name='foobar'),
287+
)
288+
request = pretend.stub(
289+
POST={'confirm_version': ''},
290+
method="POST",
291+
db=pretend.stub(delete=pretend.call_recorder(lambda a: None)),
292+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
293+
session=pretend.stub(
294+
flash=pretend.call_recorder(lambda *a, **kw: None)
295+
),
296+
)
297+
view = views.ManageProjectRelease(release, request)
298+
299+
result = view.delete_project_release()
300+
301+
assert isinstance(result, HTTPSeeOther)
302+
assert result.headers["Location"] == "/the-redirect"
303+
304+
assert request.db.delete.calls == []
305+
assert request.session.flash.calls == [
306+
pretend.call(
307+
"Must confirm the request.", queue='error'
308+
)
309+
]
310+
assert request.route_path.calls == [
311+
pretend.call(
312+
'manage.project.release',
313+
project_name=release.project.name,
314+
version=release.version,
315+
)
316+
]
317+
318+
def test_delete_project_release_bad_confirm(self):
319+
release = pretend.stub(
320+
version='1.2.3',
321+
project=pretend.stub(name='foobar'),
322+
)
323+
request = pretend.stub(
324+
POST={'confirm_version': 'invalid'},
325+
method="POST",
326+
db=pretend.stub(delete=pretend.call_recorder(lambda a: None)),
327+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
328+
session=pretend.stub(
329+
flash=pretend.call_recorder(lambda *a, **kw: None)
330+
),
331+
)
332+
view = views.ManageProjectRelease(release, request)
333+
334+
result = view.delete_project_release()
335+
336+
assert isinstance(result, HTTPSeeOther)
337+
assert result.headers["Location"] == "/the-redirect"
338+
339+
assert request.db.delete.calls == []
340+
assert request.session.flash.calls == [
341+
pretend.call(
342+
"Could not delete release - " +
343+
f"'invalid' is not the same as {release.version!r}",
344+
queue="error",
345+
)
346+
]
347+
assert request.route_path.calls == [
348+
pretend.call(
349+
'manage.project.release',
350+
project_name=release.project.name,
351+
version=release.version,
352+
)
353+
]
354+
355+
def test_delete_project_release_file(self, monkeypatch):
356+
release_file = pretend.stub(
357+
filename='foo-bar.tar.gz',
358+
id=str(uuid.uuid4()),
359+
)
360+
release = pretend.stub(
361+
version='1.2.3',
362+
project=pretend.stub(name='foobar'),
363+
)
364+
request = pretend.stub(
365+
POST={
366+
'confirm_filename': release_file.filename,
367+
'file_id': release_file.id,
368+
},
369+
method="POST",
370+
db=pretend.stub(
371+
delete=pretend.call_recorder(lambda a: None),
372+
add=pretend.call_recorder(lambda a: None),
373+
query=lambda a: pretend.stub(
374+
filter=lambda *a: pretend.stub(one=lambda: release_file),
375+
),
376+
),
377+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
378+
session=pretend.stub(
379+
flash=pretend.call_recorder(lambda *a, **kw: None)
380+
),
381+
user=pretend.stub(),
382+
remote_addr=pretend.stub(),
383+
)
384+
journal_obj = pretend.stub()
385+
journal_cls = pretend.call_recorder(lambda **kw: journal_obj)
386+
monkeypatch.setattr(views, 'JournalEntry', journal_cls)
387+
388+
view = views.ManageProjectRelease(release, request)
389+
390+
result = view.delete_project_release_file()
391+
392+
assert isinstance(result, HTTPSeeOther)
393+
assert result.headers["Location"] == "/the-redirect"
394+
395+
assert request.session.flash.calls == [
396+
pretend.call(
397+
f"Successfully deleted file {release_file.filename!r}.",
398+
queue="success",
399+
)
400+
]
401+
assert request.db.delete.calls == [pretend.call(release_file)]
402+
assert request.db.add.calls == [pretend.call(journal_obj)]
403+
assert journal_cls.calls == [
404+
pretend.call(
405+
name=release.project.name,
406+
action=f"remove file {release_file.filename}",
407+
version=release.version,
408+
submitted_by=request.user,
409+
submitted_from=request.remote_addr,
410+
),
411+
]
412+
assert request.route_path.calls == [
413+
pretend.call(
414+
'manage.project.release',
415+
project_name=release.project.name,
416+
version=release.version,
417+
)
418+
]
419+
420+
def test_delete_project_release_file_no_confirm(self):
421+
release = pretend.stub(
422+
version='1.2.3',
423+
project=pretend.stub(name='foobar'),
424+
)
425+
request = pretend.stub(
426+
POST={'confirm_filename': ''},
427+
method="POST",
428+
db=pretend.stub(delete=pretend.call_recorder(lambda a: None)),
429+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
430+
session=pretend.stub(
431+
flash=pretend.call_recorder(lambda *a, **kw: None)
432+
),
433+
)
434+
view = views.ManageProjectRelease(release, request)
435+
436+
result = view.delete_project_release_file()
437+
438+
assert isinstance(result, HTTPSeeOther)
439+
assert result.headers["Location"] == "/the-redirect"
440+
441+
assert request.db.delete.calls == []
442+
assert request.session.flash.calls == [
443+
pretend.call(
444+
"Must confirm the request.", queue='error'
445+
)
446+
]
447+
assert request.route_path.calls == [
448+
pretend.call(
449+
'manage.project.release',
450+
project_name=release.project.name,
451+
version=release.version,
452+
)
453+
]
454+
455+
def test_delete_project_release_file_bad_confirm(self):
456+
release_file = pretend.stub(
457+
filename='foo-bar.tar.gz',
458+
id=str(uuid.uuid4()),
459+
)
460+
release = pretend.stub(
461+
version='1.2.3',
462+
project=pretend.stub(name='foobar'),
463+
)
464+
request = pretend.stub(
465+
POST={'confirm_filename': 'invalid'},
466+
method="POST",
467+
db=pretend.stub(
468+
delete=pretend.call_recorder(lambda a: None),
469+
query=lambda a: pretend.stub(
470+
filter=lambda *a: pretend.stub(one=lambda: release_file),
471+
),
472+
),
473+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
474+
session=pretend.stub(
475+
flash=pretend.call_recorder(lambda *a, **kw: None)
476+
),
477+
)
478+
view = views.ManageProjectRelease(release, request)
479+
480+
result = view.delete_project_release_file()
481+
482+
assert isinstance(result, HTTPSeeOther)
483+
assert result.headers["Location"] == "/the-redirect"
484+
485+
assert request.db.delete.calls == []
486+
assert request.session.flash.calls == [
487+
pretend.call(
488+
"Could not delete file - " +
489+
f"'invalid' is not the same as {release_file.filename!r}",
490+
queue="error",
491+
)
492+
]
493+
assert request.route_path.calls == [
494+
pretend.call(
495+
'manage.project.release',
496+
project_name=release.project.name,
497+
version=release.version,
498+
)
499+
]
500+
501+
211502
class TestManageProjectRoles:
212503

213504
def test_get_manage_project_roles(self, db_request):

tests/unit/packaging/test_models.py

+22
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,28 @@ def test_urls(self, db_session, home_page, download_url, project_urls,
254254
# TODO: It'd be nice to test for the actual ordering here.
255255
assert dict(release.urls) == dict(expected)
256256

257+
def test_acl(self, db_session):
258+
project = DBProjectFactory.create()
259+
owner1 = DBRoleFactory.create(project=project)
260+
owner2 = DBRoleFactory.create(project=project)
261+
maintainer1 = DBRoleFactory.create(
262+
project=project,
263+
role_name="Maintainer",
264+
)
265+
maintainer2 = DBRoleFactory.create(
266+
project=project,
267+
role_name="Maintainer",
268+
)
269+
release = DBReleaseFactory.create(project=project)
270+
271+
assert release.__acl__() == [
272+
(Allow, "group:admins", "admin"),
273+
(Allow, str(owner1.user.id), ["manage", "upload"]),
274+
(Allow, str(owner2.user.id), ["manage", "upload"]),
275+
(Allow, str(maintainer1.user.id), ["upload"]),
276+
(Allow, str(maintainer2.user.id), ["upload"]),
277+
]
278+
257279

258280
class TestFile:
259281

tests/unit/test_routes.py

+7
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ def add_policy(name, filename):
168168
traverse="/{project_name}",
169169
domain=warehouse,
170170
),
171+
pretend.call(
172+
"manage.project.release",
173+
"/manage/project/{project_name}/release/{version}/",
174+
factory="warehouse.packaging.models:ProjectFactory",
175+
traverse="/{project_name}/{version}",
176+
domain=warehouse,
177+
),
171178
pretend.call(
172179
"manage.project.roles",
173180
"/manage/project/{project_name}/collaboration/",

0 commit comments

Comments
 (0)