-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1731: Make extensions API available on v1 r=mythmon a=rehandalal r? Co-authored-by: Rehan Dalal <[email protected]>
- Loading branch information
Showing
7 changed files
with
195 additions
and
4 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from rest_framework import serializers | ||
|
||
from normandy.studies.models import Extension | ||
|
||
|
||
class ExtensionSerializer(serializers.ModelSerializer): | ||
xpi = serializers.FileField() | ||
|
||
class Meta: | ||
model = Extension | ||
fields = ["id", "name", "xpi", "extension_id", "version", "hash", "hash_algorithm"] | ||
read_only_fields = ["extension_id", "version", "hash", "hash_algorithm"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from django.db.models import Q | ||
|
||
from rest_framework import viewsets | ||
|
||
from normandy.base.api.filters import AliasedOrderingFilter | ||
from normandy.base.api.mixins import CachingViewsetMixin | ||
from normandy.studies.api.v1.serializers import ExtensionSerializer | ||
from normandy.studies.models import Extension | ||
|
||
|
||
class ExtensionOrderingFilter(AliasedOrderingFilter): | ||
aliases = {"id": ("id", "ID"), "name": ("name", "Name")} | ||
|
||
|
||
class ExtensionViewSet(CachingViewsetMixin, viewsets.ReadOnlyModelViewSet): | ||
queryset = Extension.objects.all() | ||
serializer_class = ExtensionSerializer | ||
filter_backends = [ExtensionOrderingFilter] | ||
|
||
def get_queryset(self): | ||
queryset = self.queryset | ||
|
||
if "text" in self.request.GET: | ||
text = self.request.GET.get("text") | ||
queryset = queryset.filter(Q(name__icontains=text) | Q(xpi__icontains=text)) | ||
|
||
return queryset |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
from urllib.parse import urlparse | ||
|
||
import pytest | ||
from pathlib import Path | ||
|
||
from django.conf import settings | ||
|
||
from normandy.base.tests import Whatever | ||
from normandy.studies.tests import ExtensionFactory | ||
|
||
|
||
@pytest.mark.django_db | ||
class TestExtensionAPI(object): | ||
@classmethod | ||
def data_path(cls, file_name): | ||
return Path(settings.BASE_DIR) / "normandy/studies/tests/data" / file_name | ||
|
||
def test_it_works(self, api_client): | ||
res = api_client.get("/api/v1/extension/") | ||
assert res.status_code == 200 | ||
assert res.data["results"] == [] | ||
|
||
def test_it_serves_extensions(self, api_client, storage): | ||
extension = ExtensionFactory(name="foo") | ||
|
||
res = api_client.get("/api/v1/extension/") | ||
assert res.status_code == 200 | ||
assert res.data["results"] == [ | ||
{ | ||
"id": extension.id, | ||
"name": "foo", | ||
"xpi": Whatever(), | ||
"extension_id": extension.extension_id, | ||
"version": extension.version, | ||
"hash": extension.hash, | ||
"hash_algorithm": extension.hash_algorithm, | ||
} | ||
] | ||
|
||
def test_list_view_includes_cache_headers(self, api_client): | ||
res = api_client.get("/api/v1/extension/") | ||
assert res.status_code == 200 | ||
# It isn't important to assert a particular value for max-age | ||
assert "max-age=" in res["Cache-Control"] | ||
assert "public" in res["Cache-Control"] | ||
|
||
def test_detail_view_includes_cache_headers(self, api_client, storage): | ||
extension = ExtensionFactory() | ||
res = api_client.get("/api/v1/extension/{id}/".format(id=extension.id)) | ||
assert res.status_code == 200 | ||
# It isn't important to assert a particular value for max-age | ||
assert "max-age=" in res["Cache-Control"] | ||
assert "public" in res["Cache-Control"] | ||
|
||
def test_list_sets_no_cookies(self, api_client): | ||
res = api_client.get("/api/v1/extension/") | ||
assert res.status_code == 200 | ||
assert "Cookies" not in res | ||
|
||
def test_detail_sets_no_cookies(self, api_client, storage): | ||
extension = ExtensionFactory() | ||
res = api_client.get("/api/v1/extension/{id}/".format(id=extension.id)) | ||
assert res.status_code == 200 | ||
assert res.client.cookies == {} | ||
|
||
def test_filtering_by_name(self, api_client, storage): | ||
matching_extension = ExtensionFactory() | ||
ExtensionFactory() # Generate another extension that will not match | ||
|
||
res = api_client.get(f"/api/v1/extension/?text={matching_extension.name}") | ||
assert res.status_code == 200 | ||
assert [ext["name"] for ext in res.data["results"]] == [matching_extension.name] | ||
|
||
def test_filtering_by_xpi(self, api_client, storage): | ||
matching_extension = ExtensionFactory() | ||
ExtensionFactory() # Generate another extension that will not match | ||
|
||
res = api_client.get(f"/api/v1/extension/?text={matching_extension.xpi}") | ||
assert res.status_code == 200 | ||
expected_path = matching_extension.xpi.url | ||
assert [urlparse(ext["xpi"]).path for ext in res.data["results"]] == [expected_path] | ||
|
||
def test_read_only(self, api_client, storage): | ||
path = self.data_path("webext-signed.xpi") | ||
with open(path, "rb") as f: | ||
res = api_client.post( | ||
"/api/v1/extension/", {"name": "test extension", "xpi": f}, format="multipart" | ||
) | ||
assert res.status_code == 405 | ||
|
||
def test_order_name(self, api_client, storage): | ||
e1 = ExtensionFactory(name="a") | ||
e2 = ExtensionFactory(name="b") | ||
|
||
res = api_client.get(f"/api/v1/extension/?ordering=name") | ||
assert res.status_code == 200 | ||
assert [r["id"] for r in res.data["results"]] == [e1.id, e2.id] | ||
|
||
res = api_client.get(f"/api/v1/extension/?ordering=-name") | ||
assert res.status_code == 200 | ||
assert [r["id"] for r in res.data["results"]] == [e2.id, e1.id] | ||
|
||
def test_order_id(self, api_client, storage): | ||
e1 = ExtensionFactory() | ||
e2 = ExtensionFactory() | ||
assert e1.id < e2.id | ||
|
||
res = api_client.get(f"/api/v1/extension/?ordering=id") | ||
assert res.status_code == 200 | ||
assert [r["id"] for r in res.data["results"]] == [e1.id, e2.id] | ||
|
||
res = api_client.get(f"/api/v1/extension/?ordering=-id") | ||
assert res.status_code == 200 | ||
assert [r["id"] for r in res.data["results"]] == [e2.id, e1.id] | ||
|
||
def test_order_bogus(self, api_client, storage): | ||
"""Test that filtering by an unknown key doesn't change the sort order""" | ||
ExtensionFactory() | ||
ExtensionFactory() | ||
|
||
res = api_client.get(f"/api/v1/extension/?ordering=bogus") | ||
assert res.status_code == 200 | ||
first_ordering = [r["id"] for r in res.data["results"]] | ||
|
||
res = api_client.get(f"/api/v1/extension/?ordering=-bogus") | ||
assert res.status_code == 200 | ||
assert [r["id"] for r in res.data["results"]] == first_ordering |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import pytest | ||
|
||
from normandy.studies.api.v1.serializers import ExtensionSerializer | ||
from normandy.studies.tests import ExtensionFactory | ||
|
||
|
||
@pytest.mark.django_db() | ||
class TestExtensionSerializer: | ||
def test_it_works(self, storage): | ||
extension = ExtensionFactory() | ||
serializer = ExtensionSerializer(extension) | ||
|
||
assert serializer.data == { | ||
"id": extension.id, | ||
"name": extension.name, | ||
"xpi": extension.xpi.url, | ||
"extension_id": extension.extension_id, | ||
"version": extension.version, | ||
"hash": extension.hash, | ||
"hash_algorithm": extension.hash_algorithm, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,25 @@ | ||
from django.conf.urls import url, include | ||
|
||
from rest_framework import routers | ||
|
||
from normandy.base.api.routers import MixedViewRouter | ||
from normandy.studies.api.v1 import views as v1_views | ||
from normandy.studies.api.v2 import views as v2_views | ||
from normandy.studies.api.v3 import views as v3_views | ||
|
||
|
||
# API Router | ||
v2_router = routers.SimpleRouter() | ||
v1_router = MixedViewRouter() | ||
v1_router.register("extension", v1_views.ExtensionViewSet) | ||
|
||
v2_router = MixedViewRouter() | ||
v2_router.register("extension", v2_views.ExtensionViewSet) | ||
|
||
v3_router = routers.SimpleRouter() | ||
v3_router = MixedViewRouter() | ||
v3_router.register("extension", v3_views.ExtensionViewSet) | ||
|
||
app_name = "studies" | ||
|
||
urlpatterns = [ | ||
url(r"^api/v1/", include(v1_router.urls)), | ||
url(r"^api/v2/", include(v2_router.urls)), | ||
url(r"^api/v3/", include(v3_router.urls)), | ||
] |