Skip to content

Commit

Permalink
Merge #1731
Browse files Browse the repository at this point in the history
1731: Make extensions API available on v1 r=mythmon a=rehandalal

r?

Co-authored-by: Rehan Dalal <[email protected]>
  • Loading branch information
bors[bot] and rehandalal committed Feb 11, 2019
2 parents f060dd6 + 0cdebd6 commit 905669c
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 4 deletions.
Empty file.
12 changes: 12 additions & 0 deletions normandy/studies/api/v1/serializers.py
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"]
27 changes: 27 additions & 0 deletions normandy/studies/api/v1/views.py
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.
127 changes: 127 additions & 0 deletions normandy/studies/tests/api/v1/test_api.py
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
21 changes: 21 additions & 0 deletions normandy/studies/tests/api/v1/test_serializers.py
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,
}
12 changes: 8 additions & 4 deletions normandy/studies/urls.py
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)),
]

0 comments on commit 905669c

Please sign in to comment.