Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement 'latest' API and release-view endpoints #8615

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cefbbce
Add 'latest' redirect to main project view
bskinn Sep 25, 2020
d995624
Switch to HTTP 307 for the 'latest' redirect
bskinn Sep 28, 2020
1efceae
Fix routes-check test and add latest_slash
bskinn Sep 28, 2020
ae850ae
Apply reformat
bskinn Sep 28, 2020
9c7818c
Refactor project JSON tests to use fixtures
bskinn Sep 29, 2020
8a9bde9
Complete API routes and logic
bskinn Sep 29, 2020
70d7204
Add 'latest' routes to test_routes.py
bskinn Sep 30, 2020
187addb
Add initial 'latest' redirect test
bskinn Sep 30, 2020
728c6d4
Refactor release check to helper method
bskinn Sep 30, 2020
b961397
Complete latest tests, add unstable tests
bskinn Sep 30, 2020
ab7f284
Reorder project_with_pre release creation
bskinn Sep 30, 2020
8967523
Add API tests for latest-stable
bskinn Sep 30, 2020
75b19bc
Add 'project not found' and _slash tests
bskinn Sep 30, 2020
be00837
Reorder slash test parametrization for readability
bskinn Sep 30, 2020
0ee0178
Refactor query logic to Project model
bskinn Oct 1, 2020
eaa9971
Add routes/views for web view redirects
bskinn Oct 2, 2020
0f1efaa
Refactor dummy project fixtures to conftest.py
bskinn Oct 2, 2020
51662d4
Add tests for release view redirects
bskinn Oct 2, 2020
5823118
1) Start docs work
bskinn Oct 4, 2020
36a9be3
Merge remote-tracking branch 'upstream/main' into latest-json
bskinn Apr 20, 2021
1da81c2
Convert 'latest' JSON endpoint to direct result
bskinn Apr 21, 2021
930a66d
Rework stable and unstable to direct returns.
bskinn Apr 21, 2021
d81ecc9
Refactor latest version lookup for missing release
bskinn Apr 22, 2021
ede8041
Switch 'latest JSON' tests to mocked responses
bskinn Apr 22, 2021
b87a826
Merge remote-tracking branch 'upstream/main' into latest-json
bskinn Apr 22, 2021
c57792c
Refactor check functions to class-scope fixtures
bskinn Apr 23, 2021
ed0baa1
Switch JSON database query to .one()
bskinn Apr 23, 2021
1cb61d6
Switch 'latest' properties to return Releases
bskinn Apr 27, 2021
ee873dd
Remove unnecessary scope="function" args
bskinn May 17, 2021
ea27efa
Merge branch 'main' into latest-json
bskinn May 17, 2021
b1dfd94
Merge branch 'latest-json' into latest-json-pr-review
bskinn May 17, 2021
0306080
Revise JSON docs to remove 'redirect' mentions
bskinn May 17, 2021
dc83830
Add guard against a nonexisting 'latest' release
bskinn May 17, 2021
a8a0cf4
Add 'latest' views tests for no-release projects
bskinn May 17, 2021
9415413
Streamline release handling in latest JSON calls
bskinn May 17, 2021
688d842
Remove redundant dbquery in 'latest' JSON
bskinn May 17, 2021
58b13aa
Merge pull request #1 from bskinn/latest-json-pr-review
bskinn May 18, 2021
c6f664a
Add sidestep for pypa/pip#9644
bskinn May 18, 2021
715816c
Revert "Add sidestep for pypa/pip#9644"
bskinn May 18, 2021
b66109f
Merge branch 'main' into latest-json
bskinn May 18, 2021
9957daa
Merge remote-tracking branch 'upstream/main' into latest-json
bskinn May 19, 2021
4f0b075
Merge branch 'main' into latest-json
bskinn May 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import os.path
import xmlrpc.client

from collections import defaultdict
from collections import defaultdict, namedtuple
from contextlib import contextmanager
from unittest import mock

Expand All @@ -36,6 +36,7 @@
from warehouse.metrics import IMetricsService

from .common.db import Session
from .common.db.packaging import ProjectFactory, ReleaseFactory


def pytest_collection_modifyitems(items):
Expand Down Expand Up @@ -337,3 +338,49 @@ def monkeypatch_session():
m = MonkeyPatch()
yield m
m.undo()


# Standardized dummy projects for testing version-search
# behavior under different stable/prerelease circumstances.
# In particular, created to support the 'latest' endpoints of
# https://github.com/pypa/warehouse/pull/8615

ProjectData = namedtuple("ProjectData", ["project", "latest_stable", "latest_pre"])


@pytest.fixture(scope="function")
bskinn marked this conversation as resolved.
Show resolved Hide resolved
def project_no_pre():
project = ProjectFactory.create()

ReleaseFactory.create(project=project, version="1.0")
ReleaseFactory.create(project=project, version="2.0")
latest_stable = ReleaseFactory.create(project=project, version="3.0")

return ProjectData(project=project, latest_stable=latest_stable, latest_pre=None)


@pytest.fixture(scope="function")
def project_with_pre():
project = ProjectFactory.create()

ReleaseFactory.create(project=project, version="1.0")
ReleaseFactory.create(project=project, version="2.0")

latest_stable = ReleaseFactory.create(project=project, version="3.0")
latest_pre = ReleaseFactory.create(project=project, version="4.0.dev0")

return ProjectData(
project=project, latest_stable=latest_stable, latest_pre=latest_pre
)


@pytest.fixture(scope="function")
def project_only_pre():
project = ProjectFactory.create()

ReleaseFactory.create(project=project, version="1.0.dev0")
ReleaseFactory.create(project=project, version="2.0.dev0")

latest_pre = ReleaseFactory.create(project=project, version="3.0.dev0")

return ProjectData(project=project, latest_stable=None, latest_pre=latest_pre)
182 changes: 151 additions & 31 deletions tests/unit/legacy/api/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from collections import OrderedDict

import pretend
import pytest

from pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound

Expand Down Expand Up @@ -66,57 +67,41 @@ def test_missing_release(self, db_request):
assert isinstance(resp, HTTPNotFound)
_assert_has_cors_headers(resp.headers)

def test_calls_release_detail(self, monkeypatch, db_request):
project = ProjectFactory.create()

ReleaseFactory.create(project=project, version="1.0")
ReleaseFactory.create(project=project, version="2.0")

release = ReleaseFactory.create(project=project, version="3.0")

def test_calls_release_detail(self, monkeypatch, db_request, project_no_pre):
response = pretend.stub()
json_release = pretend.call_recorder(lambda ctx, request: response)
monkeypatch.setattr(json, "json_release", json_release)

resp = json.json_project(project, db_request)
resp = json.json_project(project_no_pre.project, db_request)

assert resp is response
assert json_release.calls == [pretend.call(release, db_request)]

def test_with_prereleases(self, monkeypatch, db_request):
project = ProjectFactory.create()

ReleaseFactory.create(project=project, version="1.0")
ReleaseFactory.create(project=project, version="2.0")
ReleaseFactory.create(project=project, version="4.0.dev0")

release = ReleaseFactory.create(project=project, version="3.0")
assert json_release.calls == [
pretend.call(project_no_pre.latest_stable, db_request)
]

def test_with_prereleases(self, monkeypatch, db_request, project_with_pre):
response = pretend.stub()
json_release = pretend.call_recorder(lambda ctx, request: response)
monkeypatch.setattr(json, "json_release", json_release)

resp = json.json_project(project, db_request)
resp = json.json_project(project_with_pre.project, db_request)

assert resp is response
assert json_release.calls == [pretend.call(release, db_request)]

def test_only_prereleases(self, monkeypatch, db_request):
project = ProjectFactory.create()

ReleaseFactory.create(project=project, version="1.0.dev0")
ReleaseFactory.create(project=project, version="2.0.dev0")

release = ReleaseFactory.create(project=project, version="3.0.dev0")
assert json_release.calls == [
pretend.call(project_with_pre.latest_stable, db_request)
]

def test_only_prereleases(self, monkeypatch, db_request, project_only_pre):
response = pretend.stub()
json_release = pretend.call_recorder(lambda ctx, request: response)
monkeypatch.setattr(json, "json_release", json_release)

resp = json.json_project(project, db_request)
resp = json.json_project(project_only_pre.project, db_request)

assert resp is response
assert json_release.calls == [pretend.call(release, db_request)]
assert json_release.calls == [
pretend.call(project_only_pre.latest_pre, db_request)
]

def test_all_releases_yanked(self, monkeypatch, db_request):
"""
Expand Down Expand Up @@ -206,6 +191,141 @@ def test_normalizing_redirects(self, db_request):
assert resp.headers["Location"] == "/project/the-redirect"


class TestJSONLatestReleases:
@pytest.fixture
def check_json_release(self, monkeypatch):
response = pretend.stub()
json_release = pretend.call_recorder(lambda ctx, request: response)
monkeypatch.setattr(json, "json_release", json_release)

def check_function(db_request, project, release, endpoint):
resp = getattr(json, endpoint)(project, db_request)

assert resp is response
assert json_release.calls == [pretend.call(release, db_request)]

return check_function

def test_latest_no_pre(self, db_request, project_no_pre, check_json_release):
"""Confirm 'latest' gives latest-stable for project with no pre-releases."""
check_json_release(
db_request,
project_no_pre.project,
project_no_pre.latest_stable,
"json_latest",
)

def test_latest_with_pre(self, db_request, project_with_pre, check_json_release):
"""Confirm 'latest' gives latest-stable with both stable and pre-releases."""
check_json_release(
db_request,
project_with_pre.project,
project_with_pre.latest_stable,
"json_latest",
)

def test_latest_only_pre(self, db_request, project_only_pre, check_json_release):
"""Confirm 'latest' gives latest-pre for project with only pre-releases."""
check_json_release(
db_request,
project_only_pre.project,
project_only_pre.latest_pre,
"json_latest",
)

def test_latest_stable_no_pre(self, db_request, project_no_pre, check_json_release):
"""Confirm 'latest-stable' gives latest-stable with no pre-releases."""
check_json_release(
db_request,
project_no_pre.project,
project_no_pre.latest_stable,
"json_latest_stable",
)

def test_latest_stable_with_pre(
self, db_request, project_with_pre, check_json_release
):
"""Confirm 'latest-stable' gives latest-stable with no pre-releases."""
check_json_release(
db_request,
project_with_pre.project,
project_with_pre.latest_stable,
"json_latest_stable",
)

def test_latest_stable_only_pre(self, db_request, project_only_pre):
"""Confirm 'latest-stable' fails for project with no pre-releases."""
db_request.route_path = pretend.call_recorder(
lambda *a, **kw: "/project/the-redirect"
)

resp = json.json_latest_stable(project_only_pre.project, db_request)
assert isinstance(resp, HTTPNotFound)

def test_latest_unstable_no_pre(
self, db_request, project_no_pre, check_json_release
):
check_json_release(
db_request,
project_no_pre.project,
project_no_pre.latest_stable,
"json_latest_unstable",
)

def test_latest_unstable_with_pre(
self, db_request, project_with_pre, check_json_release
):
check_json_release(
db_request,
project_with_pre.project,
project_with_pre.latest_pre,
"json_latest_unstable",
)

def test_latest_unstable_only_pre(
self, db_request, project_only_pre, check_json_release
):
check_json_release(
db_request,
project_only_pre.project,
project_only_pre.latest_pre,
"json_latest_unstable",
)

@pytest.mark.parametrize(
"endpoint",
["json_latest", "json_latest_stable", "json_latest_unstable"],
)
def test_missing_release(self, db_request, endpoint):
project = ProjectFactory.create()

resp = getattr(json, endpoint)(project, db_request)
assert isinstance(resp, HTTPNotFound)


class TestJSONLatestSlash:
@pytest.mark.parametrize(
("endpoint", "route"),
[
("json_latest_slash", "legacy.api.json.latest"),
("json_latest_stable_slash", "legacy.api.json.latest_stable"),
("json_latest_unstable_slash", "legacy.api.json.latest_unstable"),
],
)
def test_normalizing_redirects(self, db_request, endpoint, route):
project = ProjectFactory.create()

db_request.route_path = pretend.call_recorder(
lambda *a, **kw: "/project/the-redirect"
)

resp = getattr(json, endpoint)(project, db_request)

assert isinstance(resp, HTTPMovedPermanently)
assert db_request.route_path.calls == [pretend.call(route, name=project.name)]
assert resp.headers["Location"] == "/project/the-redirect"


class TestJSONRelease:
def test_normalizing_redirects(self, db_request):
project = ProjectFactory.create()
Expand Down
Loading