import datetime
import os
import sys
import unittest
from unittest import mock

import akismet


class AkismetTests(unittest.TestCase):
    api_key = os.getenv("TEST_AKISMET_API_KEY")
    blog_url = os.getenv("TEST_AKISMET_BLOG_URL")

    api_key_env_var = "PYTHON_AKISMET_API_KEY"
    blog_url_env_var = "PYTHON_AKISMET_BLOG_URL"

    def setUp(self):
        self.api = akismet.Akismet(key=self.api_key, blog_url=self.blog_url)


class AkismetConfigurationTests(AkismetTests):
    """
    Tests configuration of the Akismet class.

    """

    def test_config_from_args(self):
        """
        Configuring via explicit arguments succeeds.

        """
        api = akismet.Akismet(key=self.api_key, blog_url=self.blog_url)
        self.assertEqual(self.api_key, api.api_key)
        self.assertEqual(self.blog_url, api.blog_url)

    def test_bad_config_args(self):
        """
        Configuring with bad arguments fails.

        """
        with self.assertRaises(akismet.APIKeyError):
            akismet.Akismet(key="invalid", blog_url="http://invalid")

    def test_config_from_env(self):
        """
        Configuring via environment variables succeeds.

        """
        try:
            os.environ[self.api_key_env_var] = self.api_key
            os.environ[self.blog_url_env_var] = self.blog_url

            api = akismet.Akismet(key=None, blog_url=None)
            self.assertEqual(self.api_key, api.api_key)
            self.assertEqual(self.blog_url, api.blog_url)

            api = akismet.Akismet()
            self.assertEqual(self.api_key, api.api_key)
            self.assertEqual(self.blog_url, api.blog_url)
        finally:
            os.environ[self.api_key_env_var] = ""
            os.environ[self.blog_url_env_var] = ""

    def test_bad_config_env(self):
        """
        Configuring with bad environment variables fails.

        """
        try:
            os.environ[self.api_key_env_var] = "invalid"
            os.environ[self.blog_url_env_var] = "http://invalid"
            with self.assertRaises(akismet.APIKeyError):
                akismet.Akismet()
        finally:
            os.environ[self.api_key_env_var] = ""
            os.environ[self.blog_url_env_var] = ""

    def test_bad_url(self):
        """
        Configuring with a bad URL fails.

        """
        bad_urls = (
            "example.com",
            "ftp://example.com",
            "www.example.com",
            "http//example.com",
            "https//example.com",
        )
        for url in bad_urls:
            with self.assertRaises(akismet.ConfigurationError):
                akismet.Akismet(key=self.api_key, blog_url=url)

    def test_missing_config(self):
        """
        Instantiating without any configuration fails.

        """
        with self.assertRaises(akismet.ConfigurationError):
            akismet.Akismet(key=None, blog_url=None)
        with self.assertRaises(akismet.ConfigurationError):
            akismet.Akismet()

    def test_user_agent(self):
        """
        The Akismet class creates the correct user-agent string.

        """
        api = akismet.Akismet(key=self.api_key, blog_url=self.blog_url)
        expected_agent = "Python/{} | akismet.py/{}".format(
            "{}.{}".format(*sys.version_info[:2]), akismet.__version__
        )
        self.assertEqual(expected_agent, api.user_agent_header["User-Agent"])


class AkismetAPITests(AkismetTests):
    """
    Tests implementation of the Akismet API.

    """

    base_kwargs = {
        "user_ip": "127.0.0.1",
        "user_agent": "Mozilla",
        # Always send this when testing; Akismet recognizes it as a
        # test query and does not train/learn from it.
        "is_test": 1,
    }

    def test_verify_key_valid(self):
        """
        The verify_key operation succeeds with a valid key and URL.

        """
        self.assertTrue(akismet.Akismet.verify_key(self.api_key, self.blog_url))

    def test_verify_key_invalid(self):
        """
        The verify_key operation fails with an invalid key and URL.

        """
        self.assertFalse(akismet.Akismet.verify_key("invalid", "http://invalid"))

    def test_comment_check_spam(self):
        """
        The comment_check method correctly identifies spam.

        """
        check_kwargs = {
            # Akismet guarantees this will be classified spam.
            "comment_author": "viagra-test-123",
            **self.base_kwargs,
        }
        self.assertTrue(self.api.comment_check(**check_kwargs))

    def test_comment_check_not_spam(self):
        """
        The comment_check method correctly identifies non-spam.

        """
        check_kwargs = {
            # Akismet guarantees this will not be classified spam.
            "user_role": "administrator",
            **self.base_kwargs,
        }
        self.assertFalse(self.api.comment_check(**check_kwargs))

    def test_submit_spam(self):
        """
        The submit_spam method succeeds.

        """
        spam_kwargs = {
            "comment_type": "comment",
            "comment_author": "viagra-test-123",
            "comment_content": "viagra-test-123",
            **self.base_kwargs,
        }
        self.assertTrue(self.api.submit_spam(**spam_kwargs))

    def test_submit_ham(self):
        """
        The submit_ham method succeeds.

        """
        ham_kwargs = {
            "comment_type": "comment",
            "comment_author": "Legitimate Author",
            "comment_content": "This is a legitimate comment.",
            "user_role": "administrator",
            **self.base_kwargs,
        }
        self.assertTrue(self.api.submit_ham(**ham_kwargs))

    def test_unexpected_verify_key_response(self):
        """
        Unexpected verify_key API responses are correctly handled.

        """
        post_mock = mock.MagicMock()
        with mock.patch("requests.post", post_mock):
            with self.assertRaises(akismet.ProtocolError):
                akismet.Akismet.verify_key(self.api_key, self.blog_url)

    def test_unexpected_comment_check_response(self):
        """
        Unexpected comment_check API responses are correctly handled.

        """
        post_mock = mock.MagicMock()
        with mock.patch("requests.post", post_mock):
            with self.assertRaises(akismet.ProtocolError):
                check_kwargs = {"comment_author": "viagra-test-123", **self.base_kwargs}
                self.api.comment_check(**check_kwargs)

    def test_unexpected_submit_spam_response(self):
        """
        Unexpected submit_spam API responses are correctly handled.

        """
        post_mock = mock.MagicMock()
        with mock.patch("requests.post", post_mock):
            with self.assertRaises(akismet.ProtocolError):
                spam_kwargs = {
                    "comment_type": "comment",
                    "comment_author": "viagra-test-123",
                    "comment_content": "viagra-test-123",
                    **self.base_kwargs,
                }
                self.api.submit_spam(**spam_kwargs)

    def test_unexpected_submit_ham_response(self):
        """
        Unexpected submit_ham API responses are correctly handled.

        """
        post_mock = mock.MagicMock()
        with mock.patch("requests.post", post_mock):
            with self.assertRaises(akismet.ProtocolError):
                ham_kwargs = {
                    "comment_type": "comment",
                    "comment_author": "Legitimate Author",
                    "comment_content": "This is a legitimate comment.",
                    "user_role": "administrator",
                    **self.base_kwargs,
                }
                self.api.submit_ham(**ham_kwargs)


class AkismetRequestTests(AkismetTests):
    """
    Tests the requests constructed by the Akismet class.

    """

    def _get_mock(self, text):
        """
        Create a mock for requests.post() returning expected text.

        """
        post_mock = mock.MagicMock()
        post_mock.return_value.text = text
        return post_mock

    def _mock_request(self, method, endpoint, text, method_kwargs):
        """
        Issue a mocked request and verify requests.post() was called
        with the correct arguments.

        """
        method_kwargs.update(user_ip="127.0.0.1", user_agent="Mozilla", is_test=1)
        expected_kwargs = {"blog": self.blog_url, **method_kwargs}
        post_mock = self._get_mock(text)
        with mock.patch("requests.post", post_mock):
            getattr(self.api, method)(**method_kwargs)
            post_mock.assert_called_with(
                endpoint.format(self.api_key),
                data=expected_kwargs,
                headers=akismet.Akismet.user_agent_header,
            )

    def test_verify_key(self):
        """
        The request issued by verify_key() is correct.

        """
        post_mock = self._get_mock("valid")
        with mock.patch("requests.post", post_mock):
            akismet.Akismet.verify_key(self.api_key, self.blog_url)
            post_mock.assert_called_with(
                akismet.Akismet.VERIFY_KEY_URL,
                data={"key": self.api_key, "blog": self.blog_url},
                headers=akismet.Akismet.user_agent_header,
            )

    def test_comment_check(self):
        """
        The request issued by comment_check() is correct.

        """
        self._mock_request(
            "comment_check",
            akismet.Akismet.COMMENT_CHECK_URL,
            "true",
            {"comment_author": "viagra-test-123"},
        )

    def test_submit_spam(self):
        """
        The request issued by submit_spam() is correct.

        """
        self._mock_request(
            "submit_spam",
            akismet.Akismet.SUBMIT_SPAM_URL,
            akismet.Akismet.SUBMIT_SUCCESS_RESPONSE,
            {"comment_content": "Bad comment", "comment_author": "viagra-test-123"},
        )

    def test_submit_ham(self):
        """
        The request issued by submit_ham() is correct.

        """
        self._mock_request(
            "submit_ham",
            akismet.Akismet.SUBMIT_HAM_URL,
            akismet.Akismet.SUBMIT_SUCCESS_RESPONSE,
            {
                "comment_content": "Good comment",
                "comment_author": "Legitimate commenter",
            },
        )

    def test_full_kwargs(self):
        """
        All optional Akismet arguments are correctly passed through.

        """
        modified_timestamp = datetime.datetime.now()
        posted_timestamp = modified_timestamp - datetime.timedelta(seconds=30)
        full_kwargs = {
            "referrer": "http://www.example.com/",
            "permalink": "http://www.example.com/#comment123",
            "comment_type": "comment",
            "comment_author": "Legitimate Author",
            "comment_author_email": "email@example.com",
            "comment_author_url": "http://www.example.com/",
            "comment_content": "This is a fine comment.",
            "comment_date_gmt": posted_timestamp.isoformat(),
            "comment_post_modified_gmt": modified_timestamp.isoformat(),
            "blog_lang": "en_us",
            "blog_charset": "utf-8",
            "user_role": "administrator",
            "recheck_reason": "edit",
        }
        self._mock_request(
            "comment_check", akismet.Akismet.COMMENT_CHECK_URL, "false", full_kwargs
        )

    def test_unknown_kwargs(self):
        """
        Unknown Akismet arguments are correctly rejected.

        """
        bad_kwargs = {"bad_arg": "bad_val"}
        with self.assertRaises(akismet.UnknownArgumentError):
            self._mock_request(
                "comment_check", akismet.Akismet.COMMENT_CHECK_URL, "false", bad_kwargs
            )

[((11, 14, 11, 47), 'os.getenv', 'os.getenv', ({(11, 24, 11, 46): '"""TEST_AKISMET_API_KEY"""'}, {}), "('TEST_AKISMET_API_KEY')", False, 'import os\n'), ((12, 15, 12, 49), 'os.getenv', 'os.getenv', ({(12, 25, 12, 48): '"""TEST_AKISMET_BLOG_URL"""'}, {}), "('TEST_AKISMET_BLOG_URL')", False, 'import os\n'), ((18, 19, 18, 76), 'akismet.Akismet', 'akismet.Akismet', (), '', False, 'import akismet\n'), ((32, 14, 32, 71), 'akismet.Akismet', 'akismet.Akismet', (), '', False, 'import akismet\n'), ((109, 14, 109, 71), 'akismet.Akismet', 'akismet.Akismet', (), '', False, 'import akismet\n'), ((200, 20, 200, 36), 'unittest.mock.MagicMock', 'mock.MagicMock', ({}, {}), '()', False, 'from unittest import mock\n'), ((210, 20, 210, 36), 'unittest.mock.MagicMock', 'mock.MagicMock', ({}, {}), '()', False, 'from unittest import mock\n'), ((221, 20, 221, 36), 'unittest.mock.MagicMock', 'mock.MagicMock', ({}, {}), '()', False, 'from unittest import mock\n'), ((237, 20, 237, 36), 'unittest.mock.MagicMock', 'mock.MagicMock', ({}, {}), '()', False, 'from unittest import mock\n'), ((261, 20, 261, 36), 'unittest.mock.MagicMock', 'mock.MagicMock', ({}, {}), '()', False, 'from unittest import mock\n'), ((340, 29, 340, 52), 'datetime.datetime.now', 'datetime.datetime.now', ({}, {}), '()', False, 'import datetime\n'), ((42, 12, 42, 69), 'akismet.Akismet', 'akismet.Akismet', (), '', False, 'import akismet\n'), ((53, 18, 53, 58), 'akismet.Akismet', 'akismet.Akismet', (), '', False, 'import akismet\n'), ((57, 18, 57, 35), 'akismet.Akismet', 'akismet.Akismet', ({}, {}), '()', False, 'import akismet\n'), ((100, 12, 100, 52), 'akismet.Akismet', 'akismet.Akismet', (), '', False, 'import akismet\n'), ((102, 12, 102, 29), 'akismet.Akismet', 'akismet.Akismet', ({}, {}), '()', False, 'import akismet\n'), ((135, 24, 135, 79), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', ({(135, 51, 135, 63): 'self.api_key', (135, 65, 135, 78): 'self.blog_url'}, {}), '(self.api_key, self.blog_url)', False, 'import akismet\n'), ((142, 25, 142, 80), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', ({(142, 52, 142, 61): '"""invalid"""', (142, 63, 142, 79): '"""http://invalid"""'}, {}), "('invalid', 'http://invalid')", False, 'import akismet\n'), ((201, 13, 201, 51), 'unittest.mock.patch', 'mock.patch', ({(201, 24, 201, 39): '"""requests.post"""', (201, 41, 201, 50): 'post_mock'}, {}), "('requests.post', post_mock)", False, 'from unittest import mock\n'), ((211, 13, 211, 51), 'unittest.mock.patch', 'mock.patch', ({(211, 24, 211, 39): '"""requests.post"""', (211, 41, 211, 50): 'post_mock'}, {}), "('requests.post', post_mock)", False, 'from unittest import mock\n'), ((222, 13, 222, 51), 'unittest.mock.patch', 'mock.patch', ({(222, 24, 222, 39): '"""requests.post"""', (222, 41, 222, 50): 'post_mock'}, {}), "('requests.post', post_mock)", False, 'from unittest import mock\n'), ((238, 13, 238, 51), 'unittest.mock.patch', 'mock.patch', ({(238, 24, 238, 39): '"""requests.post"""', (238, 41, 238, 50): 'post_mock'}, {}), "('requests.post', post_mock)", False, 'from unittest import mock\n'), ((274, 13, 274, 51), 'unittest.mock.patch', 'mock.patch', ({(274, 24, 274, 39): '"""requests.post"""', (274, 41, 274, 50): 'post_mock'}, {}), "('requests.post', post_mock)", False, 'from unittest import mock\n'), ((288, 13, 288, 51), 'unittest.mock.patch', 'mock.patch', ({(288, 24, 288, 39): '"""requests.post"""', (288, 41, 288, 50): 'post_mock'}, {}), "('requests.post', post_mock)", False, 'from unittest import mock\n'), ((289, 12, 289, 67), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', ({(289, 39, 289, 51): 'self.api_key', (289, 53, 289, 66): 'self.blog_url'}, {}), '(self.api_key, self.blog_url)', False, 'import akismet\n'), ((341, 48, 341, 78), 'datetime.timedelta', 'datetime.timedelta', (), '', False, 'import datetime\n'), ((73, 16, 73, 33), 'akismet.Akismet', 'akismet.Akismet', ({}, {}), '()', False, 'import akismet\n'), ((92, 16, 92, 63), 'akismet.Akismet', 'akismet.Akismet', (), '', False, 'import akismet\n'), ((203, 16, 203, 71), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', ({(203, 43, 203, 55): 'self.api_key', (203, 57, 203, 70): 'self.blog_url'}, {}), '(self.api_key, self.blog_url)', False, 'import akismet\n')]