"""
Tests for direct interaction with the Pwned Passwords API.

"""

import mock
import requests
from django.test import override_settings

from pwned_passwords_django import api

from .base import PwnedPasswordsTests


class PwnedPasswordsAPITests(PwnedPasswordsTests):
    """
    Test interaction with the Pwned Passwords API.

    """
    def test_unicode_requirement(self):
        with self.assertRaises(TypeError):
            api.pwned_password(self.sample_password.encode('utf-8'))

    def test_compromised(self):
        """
        Compromised passwords are detected correctly.

        """
        for count in range(1, 10):
            request_mock = self._get_mock(
                response_text='{}:{}'.format(
                    self.sample_password_suffix,
                    count
                )
            )
            with mock.patch('requests.get', request_mock):
                result = api.pwned_password(self.sample_password)
                request_mock.assert_called_with(
                    url=api.API_ENDPOINT.format(
                        self.sample_password_prefix
                    ),
                    headers=self.user_agent,
                    timeout=api.REQUEST_TIMEOUT,
                )
                self.assertEqual(count, result)

    def test_not_compromised(self):
        """
        Non-compromised passwords are detected correctly.

        """
        request_mock = self._get_mock(
            response_text='{}:5'.format(
                self.sample_password_suffix.replace('A', '3')
            )
        )
        with mock.patch('requests.get', request_mock):
            result = api.pwned_password(self.sample_password)
            request_mock.assert_called_with(
                url=api.API_ENDPOINT.format(
                    self.sample_password_prefix
                ),
                headers=self.user_agent,
                timeout=api.REQUEST_TIMEOUT,
            )
            self.assertEqual(0, result)

        # The real API doesn't return a result with a zero count, but
        # test it just in case.
        request_mock = self._get_mock(
            response_text='{}:0'.format(
                self.sample_password_suffix
            )
        )
        with mock.patch('requests.get', request_mock):
            result = api.pwned_password(self.sample_password)
            request_mock.assert_called_with(
                url=api.API_ENDPOINT.format(
                    self.sample_password_prefix
                ),
                headers=self.user_agent,
                timeout=api.REQUEST_TIMEOUT,
            )
            self.assertEqual(0, result)

    def test_empty_response(self):
        """
        An empty API response is handled correctly.

        """
        request_mock = self._get_mock(
            response_text=''
        )
        with mock.patch('requests.get', request_mock):
            result = api.pwned_password(self.sample_password)
            request_mock.assert_called_with(
                url=api.API_ENDPOINT.format(
                    self.sample_password_prefix
                ),
                headers=self.user_agent,
                timeout=api.REQUEST_TIMEOUT,
            )
            self.assertEqual(0, result)

    @override_settings(PWNED_PASSWORDS_API_TIMEOUT=0.5)
    def test_timeout_override(self):
        """
        The custom request timeout setting is honored.

        """
        request_mock = self._get_mock()
        with mock.patch('requests.get', request_mock):
            api.pwned_password(self.sample_password)
            request_mock.assert_called_with(
                url=api.API_ENDPOINT.format(
                    self.sample_password_prefix
                ),
                headers=self.user_agent,
                timeout=0.5,
            )

    def test_timeout(self):
        """
        Connection timeouts to the API are handled gracefully.

        """
        request_mock = self._get_exception_mock(requests.ConnectTimeout())
        with mock.patch('requests.get', request_mock):
            result = api.pwned_password(self.sample_password)
            self.assertEqual(None, result)

    def test_http_error(self):
        """
        Non-200 HTTP responses from the API are handled gracefully.

        """
        request_mock = self._get_exception_mock(requests.HTTPError())
        with mock.patch.object(
                requests.Response,
                'raise_for_status',
                request_mock
        ):
            result = api.pwned_password(self.sample_password)
            self.assertEqual(None, result)
