import json
from datetime import datetime

from django.test import RequestFactory, override_settings
from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.test import APIRequestFactory, APITestCase, force_authenticate

from rest_framework_json_api import serializers, views
from rest_framework_json_api.utils import format_resource_type

from example.factories import AuthorFactory, CommentFactory, EntryFactory
from example.models import Author, Blog, Comment, Entry
from example.serializers import (
    AuthorBioSerializer,
    AuthorTypeSerializer,
    EntrySerializer,
)
from example.tests import TestBase
from example.views import AuthorViewSet, BlogViewSet


class TestRelationshipView(APITestCase):
    def setUp(self):
        self.author = Author.objects.create(
            name="Super powerful superhero", email="i.am@lost.com"
        )
        self.blog = Blog.objects.create(name="Some Blog", tagline="It's a blog")
        self.other_blog = Blog.objects.create(
            name="Other blog", tagline="It's another blog"
        )
        self.first_entry = Entry.objects.create(
            blog=self.blog,
            headline="headline one",
            body_text="body_text two",
            pub_date=timezone.now(),
            mod_date=timezone.now(),
            n_comments=0,
            n_pingbacks=0,
            rating=3,
        )
        self.second_entry = Entry.objects.create(
            blog=self.blog,
            headline="headline two",
            body_text="body_text one",
            pub_date=timezone.now(),
            mod_date=timezone.now(),
            n_comments=0,
            n_pingbacks=0,
            rating=1,
        )
        self.first_comment = Comment.objects.create(
            entry=self.first_entry, body="This entry is cool", author=None
        )
        self.second_comment = Comment.objects.create(
            entry=self.second_entry, body="This entry is not cool", author=self.author
        )

    def test_get_entry_relationship_blog(self):
        url = reverse(
            "entry-relationships",
            kwargs={"pk": self.first_entry.id, "related_field": "blog"},
        )
        response = self.client.get(url)
        expected_data = {
            "type": format_resource_type("Blog"),
            "id": str(self.first_entry.blog.id),
        }

        assert response.data == expected_data

    def test_get_entry_relationship_invalid_field(self):
        response = self.client.get(
            "/entries/{}/relationships/invalid_field".format(self.first_entry.id)
        )

        assert response.status_code == 404

    def test_get_blog_relationship_entry_set(self):
        response = self.client.get(
            "/blogs/{}/relationships/entry_set".format(self.blog.id)
        )
        expected_data = [
            {"type": format_resource_type("Entry"), "id": str(self.first_entry.id)},
            {"type": format_resource_type("Entry"), "id": str(self.second_entry.id)},
        ]

        assert response.data == expected_data

    @override_settings(JSON_API_FORMAT_RELATED_LINKS="dasherize")
    def test_get_blog_relationship_entry_set_with_formatted_link(self):
        response = self.client.get(
            "/blogs/{}/relationships/entry-set".format(self.blog.id)
        )
        expected_data = [
            {"type": format_resource_type("Entry"), "id": str(self.first_entry.id)},
            {"type": format_resource_type("Entry"), "id": str(self.second_entry.id)},
        ]

        assert response.data == expected_data

    def test_put_entry_relationship_blog_returns_405(self):
        url = "/entries/{}/relationships/blog".format(self.first_entry.id)
        response = self.client.put(url, data={})
        assert response.status_code == 405

    def test_patch_invalid_entry_relationship_blog_returns_400(self):
        url = "/entries/{}/relationships/blog".format(self.first_entry.id)
        response = self.client.patch(url, data={"data": {"invalid": ""}})
        assert response.status_code == 400

    def test_relationship_view_errors_format(self):
        url = "/entries/{}/relationships/blog".format(self.first_entry.id)
        response = self.client.patch(url, data={"data": {"invalid": ""}})
        assert response.status_code == 400

        result = json.loads(response.content.decode("utf-8"))

        assert "data" not in result
        assert "errors" in result

    def test_get_empty_to_one_relationship(self):
        url = "/comments/{}/relationships/author".format(self.first_entry.id)
        response = self.client.get(url)
        expected_data = None

        assert response.data == expected_data

    def test_get_to_many_relationship_self_link(self):
        url = "/authors/{}/relationships/comments".format(self.author.id)

        response = self.client.get(url)
        expected_data = {
            "links": {"self": "http://testserver/authors/1/relationships/comments"},
            "data": [
                {
                    "id": str(self.second_comment.id),
                    "type": format_resource_type("Comment"),
                }
            ],
        }
        assert json.loads(response.content.decode("utf-8")) == expected_data

    def test_patch_to_one_relationship(self):
        url = "/entries/{}/relationships/blog".format(self.first_entry.id)
        request_data = {
            "data": {
                "type": format_resource_type("Blog"),
                "id": str(self.other_blog.id),
            }
        }
        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200, response.content.decode()
        assert response.data == request_data["data"]

        response = self.client.get(url)
        assert response.data == request_data["data"]

    def test_patch_one_to_many_relationship(self):
        url = "/blogs/{}/relationships/entry_set".format(self.first_entry.id)
        request_data = {
            "data": [
                {"type": format_resource_type("Entry"), "id": str(self.first_entry.id)},
            ]
        }
        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200, response.content.decode()
        assert response.data == request_data["data"]

        response = self.client.get(url)
        assert response.data == request_data["data"]

        # retry a second time should end up with same result
        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200, response.content.decode()
        assert response.data == request_data["data"]

        response = self.client.get(url)
        assert response.data == request_data["data"]

    def test_patch_one_to_many_relaitonship_with_none(self):
        url = "/blogs/{}/relationships/entry_set".format(self.first_entry.id)
        request_data = {"data": None}
        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200, response.content.decode()
        assert response.data == []

        response = self.client.get(url)
        assert response.data == []

    def test_patch_many_to_many_relationship(self):
        url = "/entries/{}/relationships/authors".format(self.first_entry.id)
        request_data = {
            "data": [
                {"type": format_resource_type("Author"), "id": str(self.author.id)},
            ]
        }
        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200, response.content.decode()
        assert response.data == request_data["data"]

        response = self.client.get(url)
        assert response.data == request_data["data"]

        # retry a second time should end up with same result
        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200, response.content.decode()
        assert response.data == request_data["data"]

        response = self.client.get(url)
        assert response.data == request_data["data"]

    def test_post_to_one_relationship_should_fail(self):
        url = "/entries/{}/relationships/blog".format(self.first_entry.id)
        request_data = {
            "data": {
                "type": format_resource_type("Blog"),
                "id": str(self.other_blog.id),
            }
        }
        response = self.client.post(url, data=request_data)
        assert response.status_code == 405, response.content.decode()

    def test_post_to_many_relationship_with_no_change(self):
        url = "/entries/{}/relationships/comments".format(self.first_entry.id)
        request_data = {
            "data": [
                {
                    "type": format_resource_type("Comment"),
                    "id": str(self.first_comment.id),
                },
            ]
        }
        response = self.client.post(url, data=request_data)
        assert response.status_code == 204, response.content.decode()
        assert len(response.rendered_content) == 0, response.rendered_content.decode()

    def test_post_to_many_relationship_with_change(self):
        url = "/entries/{}/relationships/comments".format(self.first_entry.id)
        request_data = {
            "data": [
                {
                    "type": format_resource_type("Comment"),
                    "id": str(self.second_comment.id),
                },
            ]
        }
        response = self.client.post(url, data=request_data)
        assert response.status_code == 200, response.content.decode()

        assert request_data["data"][0] in response.data

    def test_delete_to_one_relationship_should_fail(self):
        url = "/entries/{}/relationships/blog".format(self.first_entry.id)
        request_data = {
            "data": {
                "type": format_resource_type("Blog"),
                "id": str(self.other_blog.id),
            }
        }
        response = self.client.delete(url, data=request_data)
        assert response.status_code == 405, response.content.decode()

    def test_delete_relationship_overriding_with_none(self):
        url = "/comments/{}".format(self.second_comment.id)
        request_data = {
            "data": {
                "type": "comments",
                "id": self.second_comment.id,
                "relationships": {"author": {"data": None}},
            }
        }
        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200, response.content.decode()
        assert response.data["author"] is None

    def test_delete_to_many_relationship_with_no_change(self):
        url = "/entries/{}/relationships/comments".format(self.first_entry.id)
        request_data = {
            "data": [
                {
                    "type": format_resource_type("Comment"),
                    "id": str(self.second_comment.id),
                },
            ]
        }
        response = self.client.delete(url, data=request_data)
        assert response.status_code == 204, response.content.decode()
        assert len(response.rendered_content) == 0, response.rendered_content.decode()

    def test_delete_one_to_many_relationship_with_not_null_constraint(self):
        url = "/entries/{}/relationships/comments".format(self.first_entry.id)
        request_data = {
            "data": [
                {
                    "type": format_resource_type("Comment"),
                    "id": str(self.first_comment.id),
                },
            ]
        }
        response = self.client.delete(url, data=request_data)
        assert response.status_code == 409, response.content.decode()

    def test_delete_to_many_relationship_with_change(self):
        url = "/authors/{}/relationships/comments".format(self.author.id)
        request_data = {
            "data": [
                {
                    "type": format_resource_type("Comment"),
                    "id": str(self.second_comment.id),
                },
            ]
        }
        response = self.client.delete(url, data=request_data)
        assert response.status_code == 200, response.content.decode()

    def test_new_comment_data_patch_to_many_relationship(self):
        entry = EntryFactory(blog=self.blog, authors=(self.author,))
        comment = CommentFactory(entry=entry)

        url = "/authors/{}/relationships/comments".format(self.author.id)
        request_data = {
            "data": [
                {"type": format_resource_type("Comment"), "id": str(comment.id)},
            ]
        }
        previous_response = {
            "data": [{"type": "comments", "id": str(self.second_comment.id)}],
            "links": {
                "self": "http://testserver/authors/{}/relationships/comments".format(
                    self.author.id
                )
            },
        }

        response = self.client.get(url)
        assert response.status_code == 200
        assert response.json() == previous_response

        new_patched_response = {
            "data": [{"type": "comments", "id": str(comment.id)}],
            "links": {
                "self": "http://testserver/authors/{}/relationships/comments".format(
                    self.author.id
                )
            },
        }

        response = self.client.patch(url, data=request_data)
        assert response.status_code == 200
        assert response.json() == new_patched_response

        assert Comment.objects.filter(id=self.second_comment.id).exists()

    def test_options_entry_relationship_blog(self):
        url = reverse(
            "entry-relationships",
            kwargs={"pk": self.first_entry.id, "related_field": "blog"},
        )
        response = self.client.options(url)
        expected_data = {
            "data": {
                "name": "Entry Relationship",
                "description": "",
                "renders": ["application/vnd.api+json", "text/html"],
                "parses": [
                    "application/vnd.api+json",
                    "application/x-www-form-urlencoded",
                    "multipart/form-data",
                ],
                "allowed_methods": [
                    "GET",
                    "POST",
                    "PATCH",
                    "DELETE",
                    "HEAD",
                    "OPTIONS",
                ],
                "actions": {"POST": {}},
            }
        }
        assert response.json() == expected_data


class TestRelatedMixin(APITestCase):
    def setUp(self):
        self.author = AuthorFactory()

    def _get_view(self, kwargs):
        factory = APIRequestFactory()
        request = Request(factory.get("", content_type="application/vnd.api+json"))
        return AuthorViewSet(request=request, kwargs=kwargs)

    def test_get_related_field_name(self):
        kwargs = {"pk": self.author.id, "related_field": "bio"}
        view = self._get_view(kwargs)
        got = view.get_related_field_name()
        self.assertEqual(got, kwargs["related_field"])

    def test_get_related_instance_serializer_field(self):
        kwargs = {"pk": self.author.id, "related_field": "bio"}
        view = self._get_view(kwargs)
        got = view.get_related_instance()
        self.assertEqual(got, self.author.bio)

    def test_get_related_instance_model_field(self):
        kwargs = {"pk": self.author.id, "related_field": "id"}
        view = self._get_view(kwargs)
        got = view.get_related_instance()
        self.assertEqual(got, self.author.id)

    def test_get_related_serializer_class(self):
        kwargs = {"pk": self.author.id, "related_field": "bio"}
        view = self._get_view(kwargs)
        got = view.get_related_serializer_class()
        self.assertEqual(got, AuthorBioSerializer)

    def test_get_related_serializer_class_many(self):
        kwargs = {"pk": self.author.id, "related_field": "entries"}
        view = self._get_view(kwargs)
        got = view.get_related_serializer_class()
        self.assertEqual(got, EntrySerializer)

    def test_get_serializer_comes_from_included_serializers(self):
        kwargs = {"pk": self.author.id, "related_field": "type"}
        view = self._get_view(kwargs)
        related_serializers = view.get_serializer_class().related_serializers
        delattr(view.get_serializer_class(), "related_serializers")
        got = view.get_related_serializer_class()
        self.assertEqual(got, AuthorTypeSerializer)
        view.get_serializer_class().related_serializers = related_serializers

    def test_get_related_serializer_class_raises_error(self):
        kwargs = {"pk": self.author.id, "related_field": "unknown"}
        view = self._get_view(kwargs)
        self.assertRaises(NotFound, view.get_related_serializer_class)

    def test_retrieve_related_single_reverse_lookup(self):
        url = reverse(
            "author-related", kwargs={"pk": self.author.pk, "related_field": "bio"}
        )
        resp = self.client.get(url, data={"include": "metadata"})
        expected = {
            "data": {
                "type": "authorBios",
                "id": str(self.author.bio.id),
                "relationships": {
                    "author": {"data": {"type": "authors", "id": str(self.author.id)}},
                    "metadata": {
                        "data": {
                            "id": str(self.author.bio.metadata.id),
                            "type": "authorBioMetadata",
                        }
                    },
                },
                "attributes": {"body": str(self.author.bio.body)},
            },
            "included": [
                {
                    "attributes": {"body": str(self.author.bio.metadata.body)},
                    "id": str(self.author.bio.metadata.id),
                    "type": "authorBioMetadata",
                }
            ],
        }
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.json(), expected)

    def test_retrieve_related_single(self):
        url = reverse(
            "author-related",
            kwargs={"pk": self.author.type.pk, "related_field": "type"},
        )
        resp = self.client.get(url)
        expected = {
            "data": {
                "type": "authorTypes",
                "id": str(self.author.type.id),
                "attributes": {"name": str(self.author.type.name)},
            }
        }
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.json(), expected)

    def test_retrieve_related_many(self):
        entry = EntryFactory(authors=self.author)
        url = reverse(
            "author-related", kwargs={"pk": self.author.pk, "related_field": "entries"}
        )
        resp = self.client.get(url)

        self.assertEqual(resp.status_code, 200)
        self.assertTrue(isinstance(resp.json()["data"], list))
        self.assertEqual(len(resp.json()["data"]), 1)
        self.assertEqual(resp.json()["data"][0]["id"], str(entry.id))

    def test_retrieve_related_many_hyperlinked(self):
        comment = CommentFactory(author=self.author)
        url = reverse(
            "author-related", kwargs={"pk": self.author.pk, "related_field": "comments"}
        )
        resp = self.client.get(url)

        self.assertEqual(resp.status_code, 200)
        self.assertTrue(isinstance(resp.json()["data"], list))
        self.assertEqual(len(resp.json()["data"]), 1)
        self.assertEqual(resp.json()["data"][0]["id"], str(comment.id))

    def test_retrieve_related_None(self):
        kwargs = {"pk": self.author.pk, "related_field": "first_entry"}
        url = reverse("author-related", kwargs=kwargs)
        resp = self.client.get(url)

        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.json(), {"data": None})

    @override_settings(JSON_API_FORMAT_RELATED_LINKS="dasherize")
    def test_retrieve_related_with_formatted_link(self):
        first_entry = EntryFactory(authors=(self.author,))

        kwargs = {"pk": self.author.pk, "related_field": "first-entry"}
        url = reverse("author-related", kwargs=kwargs)
        resp = self.client.get(url)

        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.json()["data"]["id"], str(first_entry.id))


class TestValidationErrorResponses(TestBase):
    def test_if_returns_error_on_empty_post(self):
        view = BlogViewSet.as_view({"post": "create"})
        response = self._get_create_response("{}", view)
        self.assertEqual(400, response.status_code)
        expected = [
            {
                "detail": "Received document does not contain primary data",
                "status": "400",
                "source": {"pointer": "/data"},
                "code": "parse_error",
            }
        ]
        self.assertEqual(expected, response.data)

    def test_if_returns_error_on_missing_form_data_post(self):
        view = BlogViewSet.as_view({"post": "create"})
        response = self._get_create_response(
            '{"data":{"attributes":{},"type":"blogs"}}', view
        )
        self.assertEqual(400, response.status_code)
        expected = [
            {
                "status": "400",
                "detail": "This field is required.",
                "source": {"pointer": "/data/attributes/name"},
                "code": "required",
            }
        ]
        self.assertEqual(expected, response.data)

    def test_if_returns_error_on_bad_endpoint_name(self):
        view = BlogViewSet.as_view({"post": "create"})
        response = self._get_create_response(
            '{"data":{"attributes":{},"type":"bad"}}', view
        )
        self.assertEqual(409, response.status_code)
        expected = [
            {
                "detail": (
                    "The resource object's type (bad) is not the type that constitute the collection "
                    "represented by the endpoint (blogs)."
                ),
                "source": {"pointer": "/data"},
                "status": "409",
                "code": "error",
            }
        ]
        self.assertEqual(expected, response.data)

    def _get_create_response(self, data, view):
        factory = RequestFactory()
        request = factory.post("/", data, content_type="application/vnd.api+json")
        user = self.create_user("user", "pass")
        force_authenticate(request, user)
        return view(request)


class TestModelViewSet(TestBase):
    def setUp(self):
        self.author = Author.objects.create(
            name="Super powerful superhero", email="i.am@lost.com"
        )
        self.blog = Blog.objects.create(name="Some Blog", tagline="It's a blog")

    def test_no_content_response(self):
        url = "/blogs/{}".format(self.blog.pk)
        response = self.client.delete(url)
        assert response.status_code == 204, response.rendered_content.decode()
        assert len(response.rendered_content) == 0, response.rendered_content.decode()


class TestBlogViewSet(APITestCase):
    def setUp(self):
        self.blog = Blog.objects.create(name="Some Blog", tagline="It's a blog")
        self.entry = Entry.objects.create(
            blog=self.blog,
            headline="headline one",
            body_text="body_text two",
        )

    def test_get_object_gives_correct_blog(self):
        url = reverse("entry-blog", kwargs={"entry_pk": self.entry.id})
        resp = self.client.get(url)
        expected = {
            "data": {
                "attributes": {"name": self.blog.name},
                "id": "{}".format(self.blog.id),
                "links": {"self": "http://testserver/blogs/{}".format(self.blog.id)},
                "meta": {"copyright": datetime.now().year},
                "relationships": {"tags": {"data": [], "meta": {"count": 0}}},
                "type": "blogs",
            },
            "meta": {"apiDocs": "/docs/api/blogs"},
        }
        got = resp.json()
        self.assertEqual(got, expected)


class TestEntryViewSet(APITestCase):
    def setUp(self):
        self.blog = Blog.objects.create(name="Some Blog", tagline="It's a blog")
        self.first_entry = Entry.objects.create(
            blog=self.blog,
            headline="headline two",
            body_text="body_text two",
        )
        self.second_entry = Entry.objects.create(
            blog=self.blog,
            headline="headline two",
            body_text="body_text two",
        )
        self.maxDiff = None

    def test_get_object_gives_correct_entry(self):
        url = reverse("entry-featured", kwargs={"entry_pk": self.first_entry.id})
        resp = self.client.get(url)
        expected = {
            "data": {
                "attributes": {
                    "bodyText": self.second_entry.body_text,
                    "headline": self.second_entry.headline,
                    "modDate": self.second_entry.mod_date,
                    "pubDate": self.second_entry.pub_date,
                },
                "id": "{}".format(self.second_entry.id),
                "meta": {"bodyFormat": "text"},
                "relationships": {
                    "authors": {"data": [], "meta": {"count": 0}},
                    "blog": {
                        "data": {
                            "id": "{}".format(self.second_entry.blog_id),
                            "type": "blogs",
                        }
                    },
                    "blogHyperlinked": {
                        "links": {
                            "related": "http://testserver/entries/{}"
                            "/blog".format(self.second_entry.id),
                            "self": "http://testserver/entries/{}"
                            "/relationships/blog_hyperlinked".format(
                                self.second_entry.id
                            ),
                        }
                    },
                    "comments": {"data": [], "meta": {"count": 0}},
                    "commentsHyperlinked": {
                        "links": {
                            "related": "http://testserver/entries/{}"
                            "/comments".format(self.second_entry.id),
                            "self": "http://testserver/entries/{}/relationships"
                            "/comments_hyperlinked".format(self.second_entry.id),
                        }
                    },
                    "featuredHyperlinked": {
                        "links": {
                            "related": "http://testserver/entries/{}"
                            "/featured".format(self.second_entry.id),
                            "self": "http://testserver/entries/{}/relationships"
                            "/featured_hyperlinked".format(self.second_entry.id),
                        }
                    },
                    "suggested": {
                        "data": [{"id": "1", "type": "entries"}],
                        "links": {
                            "related": "http://testserver/entries/{}"
                            "/suggested/".format(self.second_entry.id),
                            "self": "http://testserver/entries/{}"
                            "/relationships/suggested".format(self.second_entry.id),
                        },
                    },
                    "suggestedHyperlinked": {
                        "links": {
                            "related": "http://testserver/entries/{}"
                            "/suggested/".format(self.second_entry.id),
                            "self": "http://testserver/entries/{}/relationships"
                            "/suggested_hyperlinked".format(self.second_entry.id),
                        }
                    },
                    "tags": {"data": [], "meta": {"count": 0}},
                },
                "type": "posts",
            }
        }
        got = resp.json()
        self.assertEqual(got, expected)


class BasicAuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ("name",)


class ReadOnlyViewSetWithCustomActions(views.ReadOnlyModelViewSet):
    queryset = Author.objects.all()
    serializer_class = BasicAuthorSerializer

    @action(detail=False, methods=["get", "post", "patch", "delete"])
    def group_action(self, request):
        return Response(status=status.HTTP_204_NO_CONTENT)

    @action(detail=True, methods=["get", "post", "patch", "delete"])
    def item_action(self, request, pk):
        return Response(status=status.HTTP_204_NO_CONTENT)


class TestReadonlyModelViewSet(TestBase):
    """
    Test if ReadOnlyModelViewSet allows to have custom actions with POST, PATCH, DELETE methods
    """

    factory = RequestFactory()
    viewset_class = ReadOnlyViewSetWithCustomActions
    media_type = "application/vnd.api+json"

    def test_group_action_allows_get(self):
        view = self.viewset_class.as_view({"get": "group_action"})
        request = self.factory.get("/")
        response = view(request)
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

    def test_group_action_allows_post(self):
        view = self.viewset_class.as_view({"post": "group_action"})
        request = self.factory.post("/", "{}", content_type=self.media_type)
        response = view(request)
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

    def test_group_action_allows_patch(self):
        view = self.viewset_class.as_view({"patch": "group_action"})
        request = self.factory.patch("/", "{}", content_type=self.media_type)
        response = view(request)
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

    def test_group_action_allows_delete(self):
        view = self.viewset_class.as_view({"delete": "group_action"})
        request = self.factory.delete("/", "{}", content_type=self.media_type)
        response = view(request)
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

    def test_item_action_allows_get(self):
        view = self.viewset_class.as_view({"get": "item_action"})
        request = self.factory.get("/")
        response = view(request, pk="1")
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

    def test_item_action_allows_post(self):
        view = self.viewset_class.as_view({"post": "item_action"})
        request = self.factory.post("/", "{}", content_type=self.media_type)
        response = view(request, pk="1")
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

    def test_item_action_allows_patch(self):
        view = self.viewset_class.as_view({"patch": "item_action"})
        request = self.factory.patch("/", "{}", content_type=self.media_type)
        response = view(request, pk="1")
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

    def test_item_action_allows_delete(self):
        view = self.viewset_class.as_view({"delete": "item_action"})
        request = self.factory.delete("/", "{}", content_type=self.media_type)
        response = view(request, pk="1")
        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
