#
# Copyright (c) 2015 Red Hat
# Licensed under The MIT License (MIT)
# http://opensource.org/licenses/MIT
#
import mock
import json
from io import BytesIO

from django.test import TestCase
from django.core.exceptions import FieldError, ValidationError
from django.utils.datastructures import MultiValueDict
from django.urls import reverse
import django.http

from rest_framework.test import APITestCase
from rest_framework import status, mixins
from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet

from contrib.drf_introspection.serializers import DynamicFieldsSerializerMixin
from .models import Label, SigKey
from pdc.apps.common import validators
from .test_utils import TestCaseWithChangeSetMixin
from . import views, viewsets

from .renderers import cached_by_argument_class
from .renderers_filters import get_filters
from .renderers_serializers import describe_serializer, SerializerFieldData


def _flatten_field_data(field_data):
    if isinstance(field_data, SerializerFieldData):
        return _flatten_field_data(field_data.values)

    if isinstance(field_data, dict):
        return {key: _flatten_field_data(value) for key, value in field_data.iteritems()}

    if isinstance(field_data, list):
        return [_flatten_field_data(value) for value in field_data]

    return field_data


class ValidatorTestCase(TestCase):
    def test_generic_validator_fail_wrong_chars(self):
        self.assertRaises(ValidationError, validators._validate_hex_string, "wrong-md5", 9)

    def test_generic_validator_fail_wrong_length(self):
        self.assertRaises(ValidationError, validators._validate_hex_string, "abcdef0123456789", 10)

    def test_validate_md5(self):
        try:
            validators.validate_md5('9aa71fd0d510520acbc13542be5c127f')
        except ValidationError:
            self.fail('Unexpected ValidationError')

    def test_validate_sha1(self):
        try:
            validators.validate_sha1('bda6fa42aef769cc6c2ae2cddd9f8ea93dee0a3f')
        except ValidationError:
            self.fail('Unexpected ValidationError')

    def test_validate_sha256(self):
        try:
            validators.validate_sha256('e1186e900b934e3d125cacd2f96ee768a1906f8ca2033f5a8acf3231652d459d')
        except ValidationError:
            self.fail('Unexpected ValidationError')

    def test_validate_sigkey(self):
        try:
            validators.validate_sigkey('fd431d51')
        except ValidationError:
            self.fail('Unexpected ValidationError')


class DynamicFieldsSerializerMixinTestCase(TestCase):
    def setUp(self):
        class fakeSerializer(object):
            def __init__(self, context=None):
                self.fields = {'a': 'a',
                               'b': 'b',
                               'c': 'c'}
                self.context = context

        class mockSerializer(DynamicFieldsSerializerMixin,
                             fakeSerializer):
            pass

        self.mock_request = mock.Mock()
        self.context = {
            'request': self.mock_request
        }
        self.serializer = mockSerializer

    def test_fields(self):
        serializer = self.serializer(fields=['a', 'b'])
        self.assertEqual(['a', 'b'], serializer.fields.keys())

    def test_bad_fields(self):
        self.assertRaises(FieldError, self.serializer, fields=['a', 'b', 'd'])
        self.assertRaises(FieldError, self.serializer, fields=['b', 'd'])

    def test_exclude_fields(self):
        serializer = self.serializer(exclude_fields=['b'])
        self.assertEqual(['a', 'c'], serializer.fields.keys())

    def test_exclude_bad_fields(self):
        self.assertRaises(FieldError, self.serializer, exclude_fields=['b', 'd'])

    def test_fields_and_exclude(self):
        serializer = self.serializer(fields=['a', 'c'], exclude_fields=['b'])
        self.assertEqual(['a', 'c'], serializer.fields.keys())
        serializer = self.serializer(fields=['a', 'b'], exclude_fields=['b'])
        self.assertEqual(['a'], serializer.fields.keys())

    def test_fields_from_context(self):
        param_dict = {'fields': ['a', 'b'], 'exclude_fields': []}
        self.mock_request.query_params = MultiValueDict(param_dict)
        serializer = self.serializer(context=self.context)
        self.assertEqual(['a', 'b'], serializer.fields.keys())

    def test_bad_fields_from_context(self):
        param_dict = {'fields': ['d'], 'exclude_fields': []}
        self.mock_request.query_params = MultiValueDict(param_dict)
        self.assertRaises(FieldError, self.serializer, context=self.context)

        param_dict = {'fields': ['a', 'b', 'd'], 'exclude_fields': []}
        self.mock_request.query_params = MultiValueDict(param_dict)
        self.assertRaises(FieldError, self.serializer, context=self.context)

    def test_exclude_fields_from_context(self):
        param_dict = {'fields': [], 'exclude_fields': ['b']}
        self.mock_request.query_params = MultiValueDict(param_dict)
        serializer = self.serializer(context=self.context)
        self.assertEqual(['a', 'c'], serializer.fields.keys())

    def test_exclude_bad_fields_from_context(self):
        param_dict = {'fields': [], 'exclude_fields': ['b', 'd']}
        self.mock_request.query_params = MultiValueDict(param_dict)
        self.assertRaises(FieldError, self.serializer, context=self.context)

    def test_fields_and_exclude_from_context(self):
        param_dict = {'fields': ['a', 'c'], 'exclude_fields': ['b']}
        self.mock_request.query_params = MultiValueDict(param_dict)
        serializer = self.serializer(context=self.context)
        self.assertEqual(['a', 'c'], serializer.fields.keys())
        param_dict = {'fields': ['a', 'b'], 'exclude_fields': ['b']}
        self.mock_request.query_params = MultiValueDict(param_dict)
        serializer = self.serializer(context=self.context)
        self.assertEqual(['a'], serializer.fields.keys())

    def test_both_init_and_context(self):
        param_dict = {'fields': ['a'], 'exclude_fields': ['b']}
        self.mock_request.query_params = MultiValueDict(param_dict)
        serializer = self.serializer(fields=['c'], exclude_fields=['b'],
                                     context=self.context)
        self.assertEqual(['a', 'c'], serializer.fields.keys())

    def test_fields_comma_separated(self):
        serializer = self.serializer(fields='a,b')
        self.assertEqual(['a', 'b'], serializer.fields.keys())

    def test_exclude_fields_comma_separated(self):
        serializer = self.serializer(exclude_fields='a,b')
        self.assertEqual(['c'], serializer.fields.keys())

    def test_fields_comma_separated_from_context(self):
        param_dict = {'fields': ['a,b']}
        self.mock_request.query_params = MultiValueDict(param_dict)
        serializer = self.serializer(context=self.context)
        self.assertEqual(['a', 'b'], serializer.fields.keys())

    def test_exclude_fields_comma_separated_from_context(self):
        param_dict = {'exclude_fields': ['a,b']}
        self.mock_request.query_params = MultiValueDict(param_dict)
        serializer = self.serializer(context=self.context)
        self.assertEqual(['c'], serializer.fields.keys())


class LabelRESTTestCase(TestCaseWithChangeSetMixin, APITestCase):
    def setUp(self):
        super(LabelRESTTestCase, self).setUp()
        self.args_label1 = {'name': 'label1', 'description': 'label1 description'}
        self.args_label2 = {'name': 'label2', 'description': 'label2 description'}
        self.args_label3 = {'name': 'label3', 'description': 'label3 description'}
        Label.objects.create(**self.args_label1)
        Label.objects.create(**self.args_label2)

    def test_list_labels(self):
        url = reverse('label-list')
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('count'), 2)
        self.assertEqual(response.data.get('results')[0].get('name'), self.args_label1.get('name'))
        self.assertEqual(response.data.get('results')[1].get('name'), self.args_label2.get('name'))

    def test_query_label_with_name(self):
        url = reverse('label-list') + '?name=label1'
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('count'), 1)
        self.assertEqual(response.data.get('results')[0].get('name'), self.args_label1.get('name'))
        self.assertEqual(response.data.get('results')[0].get('description'), self.args_label1.get('description'))

    def test_query_labels_with_multiple_names(self):
        url = reverse('label-list') + '?name=label1&name=label2'
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('count'), 2)
        self.assertEqual(response.data.get('results')[0].get('name'), self.args_label1.get('name'))
        self.assertEqual(response.data.get('results')[1].get('name'), self.args_label2.get('name'))

    def test_bulk_partial_update(self):
        data = {'1': {'description': 'desc 1'},
                '2': {'description': 'desc 2'}}
        response = self.client.patch(reverse('label-list'), data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertNumChanges([2])

    def test_bulk_delete(self):
        response = self.client.delete(reverse('label-list'), [1, 2], format='json')
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertNumChanges([2])
        self.assertEqual(Label.objects.count(), 0)

    def test_query_labels_with_none_existing_name(self):
        url = reverse('label-list') + '?name=other'
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('count'), 0)

    def test_create_label(self):
        url = reverse('label-list')
        response = self.client.post(url, format='json', data=self.args_label3)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response = self.client.get(url, format='json')
        self.assertEqual(response.data.get('count'), 3)
        self.assertEqual(response.data.get('results')[0].get('name'), self.args_label1.get('name'))
        self.assertEqual(response.data.get('results')[1].get('name'), self.args_label2.get('name'))
        self.assertEqual(response.data.get('results')[2].get('name'), self.args_label3.get('name'))

    def test_create_label_extra_fields(self):
        url = reverse('label-list')
        self.args_label3['foo'] = 'bar'
        response = self.client.post(url, format='json', data=self.args_label3)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data.get('detail'), 'Unknown fields: "foo".')

    def test_create_label_only_name(self):
        url = reverse('label-list')
        response = self.client.post(url, format='json', data={'name': 'label4'})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data, {"description": ["This field is required."]})

    def test_create_label_with_empty(self):
        url = reverse('label-list')
        response = self.client.post(url, format='json', data={})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data, {"name": ["This field is required."],
                                         "description": ["This field is required."]})

    def test_create_label_with_only_description(self):
        url = reverse('label-list')
        response = self.client.post(url, format='json', data={'description': 'description'})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data, {"name": ["This field is required."]})

    def test_create_existing_label(self):
        url = reverse('label-list')
        response = self.client.post(url, format='json', data=self.args_label1)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn(response.data, [
            {"name": ["This field must be unique."]},  # DRF v3.2 its own UniqueValidator,
            {"name": ["Label with this name already exists."]},  # v3.3 use Django's default.
            {'name': ['label with this name already exists.']},
        ])

    def test_put_update(self):
        url = reverse('label-detail', kwargs={'pk': 1})
        args = {'name': 'new name', 'description': 'new description'}
        response = self.client.put(url, format='json', data=args)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('name'), args.get('name'))
        self.assertEqual(response.data.get('description'), args.get('description'))

    def test_update_non_existing_label(self):
        url = reverse('label-detail', kwargs={'pk': 9999})
        args = {'name': 'new name', 'description': 'new description'}
        response = self.client.put(url, format='json', data=args)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        self.assertEqual(response.data, {"detail": "Not found."})

    def test_patch_update_description(self):
        url = reverse('label-detail', kwargs={'pk': 1})
        args = {'description': 'new description'}
        response = self.client.patch(url, format='json', data=args)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('name'), self.args_label1.get('name'))
        self.assertEqual(response.data.get('description'), args.get('description'))

    def test_patch_update_description_name(self):
        url = reverse('label-detail', kwargs={'pk': 1})
        args = {'name': 'new name'}
        response = self.client.patch(url, format='json', data=args)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('name'), args.get('name'))
        self.assertEqual(response.data.get('description'), self.args_label1.get('description'))

    def test_partial_update_empty(self):
        url = reverse('label-detail', kwargs={'pk': 1})
        response = self.client.patch(url, format='json', data={})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_update_to_existing_label(self):
        url = reverse('label-detail', kwargs={'pk': 1})
        response = self.client.put(url, format='json', data=self.args_label2)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_delete_label(self):
        url = reverse('label-detail', kwargs={'pk': 1})
        response = self.client.delete(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        response = self.client.get(url, format='json')
        self.assertEqual(response.data.get('detail'), 'Not found.')

    def test_delete_none_existing_label(self):
        url = reverse('label-detail', kwargs={'pk': 9999})
        response = self.client.delete(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        self.assertEqual(response.data.get('detail'), 'Not found.')


class ArchRESTTestCase(APITestCase):
    def test_list_all_arches(self):
        url = reverse('arch-list')
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 48)

    def test_create_arch(self):
        url = reverse('arch-list')
        response = self.client.post(url, format='json', data={"name": "arm"})
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['name'], "arm")

    def test_create_arch_with_long_name(self):
        url = reverse('arch-list')
        response = self.client.post(url, format='json', data={"name": "a" * 100})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)


class SigKeyRESTTestCase(TestCaseWithChangeSetMixin, APITestCase):
    def setUp(self):
        SigKey.objects.bulk_create([SigKey(key_id="1234adbf", name="A",
                                           description="icontains_A"),
                                    SigKey(key_id="f2134bca", name="B",
                                           description="icontains_B"),
                                    SigKey(key_id="d343aaaa", name="C",
                                           description="icontains_C")])
        super(SigKeyRESTTestCase, self).setUp()

    def test_list_all_sigkeys(self):
        url = reverse('sigkey-list')
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 3)

    def test_query_sigkey(self):
        url = reverse('sigkey-list')
        response = self.client.get(url, format='json', data={"name": "B"})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)

    def test_query_icontains_sigkey_(self):
        url = reverse('sigkey-list')
        response = self.client.get(url, format='json', data={
            "description": "ns_c"})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)
        self.assertEqual(response.data['results'][0]['key_id'], 'd343aaaa')

    def test_query_multi_value(self):
        response = self.client.get(reverse('sigkey-list') + '?key_id=1234adbf&key_id=f2134bca')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 2)

    def test_retrieve_sigkey(self):
        url = reverse('sigkey-detail', kwargs={'key_id': '1234adbf'})
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['key_id'], '1234adbf')

    def test_update_sigkey(self):
        url = reverse('sigkey-detail', kwargs={'key_id': '1234adbf'})
        response = self.client.put(url, format='json',
                                   data={'key_id': '1234adbf', 'name': "TEST"})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertNumChanges([1])
        self.assertEqual(response.data['name'], 'TEST')

    def test_partial_update_sigkey(self):
        url = reverse('sigkey-detail', kwargs={'key_id': '1234adbf'})
        response = self.client.patch(url, format='json', data={'name': "TEST"})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['name'], 'TEST')

    def test_can_update_key_id(self):
        response = self.client.patch(reverse('sigkey-detail', args=['1234adbf']),
                                     {'key_id': 'cafebabe'},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data.get('key_id'), 'cafebabe')
        self.assertNumChanges([1])
        response = self.client.get(reverse('sigkey-detail', args=['1234adbf']))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        response = self.client.get(reverse('sigkey-detail', args=['cafebabe']))
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_can_bulk_update(self):
        data = {'1234adbf': {'name': 'A',
                             'description': 'icontains_a',
                             'key_id': '1234adbf'}}
        response = self.client.put(reverse('sigkey-list'), data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertNumChanges([1])
        self.assertDictEqual(dict(response.data), data)

    def test_bulk_partial_update(self):
        response = self.client.patch(reverse('sigkey-list'),
                                     {'1234adbf': {'name': 'Key A'},
                                      'f2134bca': {'description': 'icontains b'}},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertNumChanges([2])
        self.assertDictEqual(dict(response.data),
                             {'1234adbf': {'key_id': '1234adbf',
                                           'name': 'Key A',
                                           'description': 'icontains_A'},
                              'f2134bca': {'key_id': 'f2134bca',
                                           'name': 'B',
                                           'description': 'icontains b'}})

    def test_partial_update_empty(self):
        url = reverse('sigkey-detail', kwargs={'key_id': '1234adbf'})
        response = self.client.patch(url, format='json', data={})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_update_missing_optional_fields_are_erased(self):
        url = reverse('sigkey-detail', kwargs={'key_id': '1234adbf'})
        response = self.client.put(url, format='json',
                                   data={'key_id': '1234adbf', 'name': "TEST"})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['name'], 'TEST')
        self.assertEqual(response.data['description'], '')
        response = self.client.put(url, format='json',
                                   data={'key_id': '1234adbf', 'description': "TEST"})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['name'], None)
        self.assertEqual(response.data['description'], 'TEST')
        self.assertNumChanges([1, 1])

    def test_delete_sigkey(self):
        url = reverse('sigkey-detail', kwargs={'key_id': '1234adbf'})
        response = self.client.delete(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

    def test_create_sigkey(self):
        url = reverse('sigkey-list')
        data = {"key_id": "abcd1234", "name": "test", "description": "test"}
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertDictEqual(dict(response.data), data)
        self.assertNumChanges([1])


class CachedByArgumentClassTestCase(TestCase):
    @cached_by_argument_class
    def cached(self, arg, call_id):
        return (arg, call_id)

    def test_same_argument(self):
        class A:
            pass

        a = A()
        self.assertEqual(self.cached(a, 1), (a, 1))
        self.assertEqual(self.cached(a, 2), (a, 1))

    def test_different_instances_same_class(self):
        class A:
            pass

        a1 = A()
        a2 = A()
        self.assertEqual(self.cached(a1, 1), (a1, 1))
        self.assertEqual(self.cached(a2, 2), (a1, 1))

    def test_different_class(self):
        class A:
            pass

        class B:
            pass

        a = A()
        b = B()
        self.assertEqual(self.cached(a, 1), (a, 1))
        self.assertEqual(self.cached(b, 2), (b, 2))

    def test_different_instances_and_classes(self):
        class A:
            pass

        class B:
            pass

        a1 = A()
        a2 = A()
        b1 = B()
        b2 = B()
        self.assertEqual(self.cached(a1, 1), (a1, 1))
        self.assertEqual(self.cached(b1, 2), (b1, 2))
        self.assertEqual(self.cached(a2, 3), (a1, 1))
        self.assertEqual(self.cached(b2, 4), (b1, 2))


class FilterDocumentingTestCase(TestCase):
    def test_filter_fields_have_no_type(self):
        class TestViewset(object):
            filter_fields = ('c', 'b', 'a')
        res = get_filters(TestViewset())
        self.assertEqual(res, ' * `a`\n * `b`\n * `c`')

    def test_filter_extra_query_params_have_no_type(self):
        class TestViewset(object):
            extra_query_params = ('c', 'b', 'a')
        res = get_filters(TestViewset())
        self.assertEqual(res, ' * `a`\n * `b`\n * `c`')

    def test_filter_set_has_details(self):
        res = get_filters(views.SigKeyViewSet())
        self.assertEqual(
            res,
            ' * `description` (string, case insensitive, substring match)\n'
            ' * `key_id` (string)\n'
            ' * `name` (string)'
        )


class SerializerDocumentingTestCase(TestCase):
    def test_describe_by_class_attr(self):
        class DummySerializer(object):
            doc_format = {"foo": "bar"}

        instance = DummySerializer()

        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(result, {'foo': 'bar'})

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(result, {'foo': 'bar'})

    def test_describe_fields(self):
        class DummySerializer(serializers.Serializer):
            str = serializers.CharField()
            int = serializers.IntegerField()

        instance = DummySerializer()

        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(_flatten_field_data(result), {
            'str': {'value': 'string'},
            'int': {'value': 'int'}
        })

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(_flatten_field_data(result), {
            'str': {'value': 'string'},
            'int': {'value': 'int'}
        })

    def test_describe_read_only_field(self):
        class DummySerializer(serializers.Serializer):
            field = serializers.CharField(read_only=True)

        instance = DummySerializer()

        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(_flatten_field_data(result), {})

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(_flatten_field_data(result), {'field': {'value': 'string'}})

    def test_describe_read_only_field_can_be_excluded(self):
        class DummySerializer(serializers.Serializer):
            field = serializers.CharField(read_only=True)

        instance = DummySerializer()
        result = describe_serializer(instance, False)
        self.assertEqual(result, {})

    def test_describe_nullable_field(self):
        class DummySerializer(serializers.Serializer):
            field = serializers.CharField(allow_null=True)

        instance = DummySerializer()
        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(_flatten_field_data(result), {
            'field': {'tags': 'nullable', 'value': 'string'}
        })

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(_flatten_field_data(result), {'field': {'tags': 'nullable', 'value': 'string'}})

    def test_describe_field_with_default_value(self):
        class DummySerializer(serializers.Serializer):
            field = serializers.CharField(required=False, default='foo')

        instance = DummySerializer()
        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(_flatten_field_data(result), {
            'field': {'tags': 'optional, default="foo"', 'value': 'string'}
        })

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(_flatten_field_data(result), {'field': {'value': 'string'}})

    def test_describe_field_with_default_from_model(self):
        default = mock.Mock()
        default.default = True
        default.help_text = 'Help text'
        DummyModel = mock.Mock()
        DummyModel._meta.get_field = lambda _: default

        class DummySerializer(serializers.Serializer):
            field = serializers.CharField(required=False)

            class Meta:
                model = DummyModel

        instance = DummySerializer()

        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(_flatten_field_data(result), {
            'field': {
                'help': 'Help text',
                'tags': 'optional, default=true, unique',
                'value': 'string'
            }
        })

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(_flatten_field_data(result), {
            'field': {
                'help': 'Help text',
                'value': 'string'
            }
        })

    def test_describe_field_with_complex_default(self):
        class DummyDefault(object):
            doc_format = 'some string format'

        class DummySerializer(serializers.Serializer):
            field = serializers.CharField(required=False, default=DummyDefault)

        instance = DummySerializer()
        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(_flatten_field_data(result), {
            'field': {
                'tags': 'optional, default="some string format"',
                'value': 'string'
            }
        })

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(_flatten_field_data(result), {'field': {'value': 'string'}})

    def test_describe_field_with_custom_type(self):
        class DummyField(serializers.Field):
            doc_format = '{"foo": "bar"}'
            writable_doc_format = '{"baz": "quux"}'

        class DummySerializer(serializers.Serializer):
            field = DummyField()

        instance = DummySerializer()

        result = describe_serializer(instance, include_read_only=True)
        self.assertEqual(_flatten_field_data(result), {'field': {'value': {'foo': 'bar'}}})

        result = describe_serializer(instance, include_read_only=False)
        self.assertEqual(_flatten_field_data(result), {'field': {'value': {'baz': 'quux'}}})

    def test_describe_nested_serializer(self):
        class DummyNestedSerializer(serializers.Serializer):
            field = serializers.CharField()

        class DummySerializer(serializers.Serializer):
            top_level = DummyNestedSerializer()

        instance = DummySerializer()
        result = describe_serializer(instance, True)
        self.assertEqual(_flatten_field_data(result), {
            'top_level': {
                'value': {
                    'field': {'value': 'string'}
                }
            }
        })

    def test_describe_nested_serializer_many(self):
        class DummyNestedSerializer(serializers.Serializer):
            field = serializers.CharField()

        class DummySerializer(serializers.Serializer):
            top_level = DummyNestedSerializer(many=True)

        instance = DummySerializer()
        result = describe_serializer(instance, True)
        self.assertEqual(_flatten_field_data(result), {
            'top_level': {
                'value': [{
                    'field': {'value': 'string'}
                }]
            }
        })


class JSONResponseFor404(APITestCase):
    def test_returns_html_without_header(self):
        response = self.client.get('/foo/bar')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        self.assertIn('<html>', response.content)

    def test_respects_accept_header(self):
        response = self.client.get('/foo/bar', HTTP_ACCEPT='application/json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        try:
            json.loads(response.content)
        except ValueError:
            self.fail('Response was not JSON')


class OrderingTestCase(APITestCase):
    fixtures = ['pdc/apps/common/fixtures/test/sigkeys.json']

    def test_ordering_single(self):
        response = self.client.get(reverse('sigkey-list'), data={'ordering': 'name'}, format='json')
        self.assertEqual(response.data['count'], 3)
        results = response.data.get('results')
        self.assertLess(results[0].get('name'), results[1].get('name'))
        self.assertLess(results[1].get('name'), results[2].get('name'))

    def test_ordering_single_reverse(self):
        response = self.client.get(reverse('sigkey-list'), data={'ordering': '-key_id'}, format='json')
        self.assertEqual(response.data['count'], 3)
        results = response.data.get('results')
        self.assertGreater(results[0].get('key_id'), results[1].get('key_id'))
        self.assertGreater(results[1].get('key_id'), results[2].get('key_id'))

    def test_ordering_multiple(self):
        response = self.client.get(reverse('sigkey-list'), data={'ordering': 'description,name'}, format='json')
        self.assertEqual(response.data['count'], 3)
        results = response.data.get('results')
        self.assertEqual(results[0].get('description'), 'A')
        self.assertEqual(results[1].get('description'), 'A')
        self.assertLess(results[0].get('name'), results[1].get('name'))
        self.assertLess(results[1].get('description'), results[2].get('description'))

    def test_ordering_bad_key(self):
        response = self.client.get(reverse('sigkey-list'), data={'ordering': 'description_,name'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertTrue('Unknown query key' in response.data.get('detail'))
        self.assertTrue('description_' in response.data.get('detail'))

        response = self.client.get(reverse('sigkey-list'), data={'ordering': 'description,name_'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertTrue('Unknown query key' in response.data.get('detail'))
        self.assertTrue('name_' in response.data.get('detail'))


class NotificationMixinTestCase(TestCase):

    def setUp(self):

        class DummySerializer(serializers.Serializer):
            nickname = serializers.CharField()
            color = serializers.CharField()

            def create(self, validated_data):
                return mock.Mock(pk=2, **validated_data)

            def update(self, obj, validated_data):
                return mock.Mock(pk=obj.pk, **validated_data)

        class GetDogMixin(object):
            def get_object(self):
                return mock.Mock(pk=1, nickname='Spot', color='black')

        class DummyView(viewsets.NotificationMixin,
                        mixins.CreateModelMixin,
                        mixins.UpdateModelMixin,
                        mixins.DestroyModelMixin,
                        GetDogMixin,
                        GenericViewSet):
            serializer_class = DummySerializer

        mapping = {
            'post': 'create',
            'put': 'update',
            'delete': 'destroy',
        }
        self.view = DummyView.as_view(mapping, basename='dummy')

    def _make_request(self, method, data=None):
        request = django.http.HttpRequest()
        request.method = method
        request._messagings = []
        request.META = {'SERVER_NAME': '0.0.0.0', 'SERVER_PORT': 80}
        if data:
            data = json.dumps(data)
            request._read_started = False
            request.META.update({'CONTENT_LENGTH': len(data), 'CONTENT_TYPE': 'application/json'})
            request._stream = BytesIO(data)
        return request

    @mock.patch('pdc.apps.common.viewsets.router')
    @mock.patch('pdc.apps.common.viewsets.reverse')
    def test_create_notification(self, mock_reverse, mock_router):
        request = self._make_request('POST', {'nickname': 'Rover', 'color': 'brown'})
        mock_reverse.return_value = '/dogs/2/'
        mock_router.registry = [('dogs', None, 'dummy')]

        response = self.view(request)

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(request._messagings,
                         [('.dogs.added', {
                             'url': 'http://0.0.0.0/dogs/2/',
                             'new_value': {
                                 'color': 'brown',
                                 'nickname': 'Rover'
                             },
                         })])
        self.assertEqual(mock_reverse.call_args_list,
                         [mock.call('dummy-detail', args=[2])])

    @mock.patch('pdc.apps.common.viewsets.router')
    @mock.patch('pdc.apps.common.viewsets.reverse')
    def test_update_notification(self, mock_reverse, mock_router):
        request = self._make_request('PUT', {'nickname': 'Fido', 'color': 'golden'})
        mock_reverse.return_value = '/dogs/1/'
        mock_router.registry = [('dogs', None, 'dummy')]

        response = self.view(request)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(request._messagings,
                         [('.dogs.changed', {
                             'url': 'http://0.0.0.0/dogs/1/',
                             'new_value': {
                                 'color': 'golden',
                                 'nickname': 'Fido'
                             },
                             'old_value': {
                                 'color': 'black',
                                 'nickname': 'Spot'
                             },
                         })])
        self.assertEqual(mock_reverse.call_args_list,
                         [mock.call('dummy-detail', args=[1])])

    @mock.patch('pdc.apps.common.viewsets.router')
    def test_delete_notification(self, mock_router):
        request = self._make_request('DELETE')
        mock_router.registry = [('dogs', None, 'dummy')]

        response = self.view(request, pk=1)

        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertEqual(request._messagings,
                         [('.dogs.removed', {
                             'url': None,
                             'old_value': {
                                 'color': 'black',
                                 'nickname': 'Spot'
                             },
                         })])