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
            )

[((150, 183), 'os.getenv', 'os.getenv', (['"""TEST_AKISMET_API_KEY"""'], {}), "('TEST_AKISMET_API_KEY')\n", (159, 183), False, 'import os\n'), ((199, 233), 'os.getenv', 'os.getenv', (['"""TEST_AKISMET_BLOG_URL"""'], {}), "('TEST_AKISMET_BLOG_URL')\n", (208, 233), False, 'import os\n'), ((372, 429), 'akismet.Akismet', 'akismet.Akismet', ([], {'key': 'self.api_key', 'blog_url': 'self.blog_url'}), '(key=self.api_key, blog_url=self.blog_url)\n', (387, 429), False, 'import akismet\n'), ((672, 729), 'akismet.Akismet', 'akismet.Akismet', ([], {'key': 'self.api_key', 'blog_url': 'self.blog_url'}), '(key=self.api_key, blog_url=self.blog_url)\n', (687, 729), False, 'import akismet\n'), ((3123, 3180), 'akismet.Akismet', 'akismet.Akismet', ([], {'key': 'self.api_key', 'blog_url': 'self.blog_url'}), '(key=self.api_key, blog_url=self.blog_url)\n', (3138, 3180), False, 'import akismet\n'), ((5853, 5869), 'unittest.mock.MagicMock', 'mock.MagicMock', ([], {}), '()\n', (5867, 5869), False, 'from unittest import mock\n'), ((6224, 6240), 'unittest.mock.MagicMock', 'mock.MagicMock', ([], {}), '()\n', (6238, 6240), False, 'from unittest import mock\n'), ((6663, 6679), 'unittest.mock.MagicMock', 'mock.MagicMock', ([], {}), '()\n', (6677, 6679), False, 'from unittest import mock\n'), ((7260, 7276), 'unittest.mock.MagicMock', 'mock.MagicMock', ([], {}), '()\n', (7274, 7276), False, 'from unittest import mock\n'), ((8017, 8033), 'unittest.mock.MagicMock', 'mock.MagicMock', ([], {}), '()\n', (8031, 8033), False, 'from unittest import mock\n'), ((10539, 10562), 'datetime.datetime.now', 'datetime.datetime.now', ([], {}), '()\n', (10560, 10562), False, 'import datetime\n'), ((1009, 1066), 'akismet.Akismet', 'akismet.Akismet', ([], {'key': '"""invalid"""', 'blog_url': '"""http://invalid"""'}), "(key='invalid', blog_url='http://invalid')\n", (1024, 1066), False, 'import akismet\n'), ((1339, 1379), 'akismet.Akismet', 'akismet.Akismet', ([], {'key': 'None', 'blog_url': 'None'}), '(key=None, blog_url=None)\n', (1354, 1379), False, 'import akismet\n'), ((1513, 1530), 'akismet.Akismet', 'akismet.Akismet', ([], {}), '()\n', (1528, 1530), False, 'import akismet\n'), ((2856, 2896), 'akismet.Akismet', 'akismet.Akismet', ([], {'key': 'None', 'blog_url': 'None'}), '(key=None, blog_url=None)\n', (2871, 2896), False, 'import akismet\n'), ((2969, 2986), 'akismet.Akismet', 'akismet.Akismet', ([], {}), '()\n', (2984, 2986), False, 'import akismet\n'), ((3894, 3949), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', (['self.api_key', 'self.blog_url'], {}), '(self.api_key, self.blog_url)\n', (3920, 3949), False, 'import akismet\n'), ((4109, 4164), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', (['"""invalid"""', '"""http://invalid"""'], {}), "('invalid', 'http://invalid')\n", (4135, 4164), False, 'import akismet\n'), ((5883, 5921), 'unittest.mock.patch', 'mock.patch', (['"""requests.post"""', 'post_mock'], {}), "('requests.post', post_mock)\n", (5893, 5921), False, 'from unittest import mock\n'), ((6254, 6292), 'unittest.mock.patch', 'mock.patch', (['"""requests.post"""', 'post_mock'], {}), "('requests.post', post_mock)\n", (6264, 6292), False, 'from unittest import mock\n'), ((6693, 6731), 'unittest.mock.patch', 'mock.patch', (['"""requests.post"""', 'post_mock'], {}), "('requests.post', post_mock)\n", (6703, 6731), False, 'from unittest import mock\n'), ((7290, 7328), 'unittest.mock.patch', 'mock.patch', (['"""requests.post"""', 'post_mock'], {}), "('requests.post', post_mock)\n", (7300, 7328), False, 'from unittest import mock\n'), ((8505, 8543), 'unittest.mock.patch', 'mock.patch', (['"""requests.post"""', 'post_mock'], {}), "('requests.post', post_mock)\n", (8515, 8543), False, 'from unittest import mock\n'), ((8969, 9007), 'unittest.mock.patch', 'mock.patch', (['"""requests.post"""', 'post_mock'], {}), "('requests.post', post_mock)\n", (8979, 9007), False, 'from unittest import mock\n'), ((9021, 9076), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', (['self.api_key', 'self.blog_url'], {}), '(self.api_key, self.blog_url)\n', (9047, 9076), False, 'import akismet\n'), ((10611, 10641), 'datetime.timedelta', 'datetime.timedelta', ([], {'seconds': '(30)'}), '(seconds=30)\n', (10629, 10641), False, 'import datetime\n'), ((2090, 2107), 'akismet.Akismet', 'akismet.Akismet', ([], {}), '()\n', (2105, 2107), False, 'import akismet\n'), ((2620, 2667), 'akismet.Akismet', 'akismet.Akismet', ([], {'key': 'self.api_key', 'blog_url': 'url'}), '(key=self.api_key, blog_url=url)\n', (2635, 2667), False, 'import akismet\n'), ((5998, 6053), 'akismet.Akismet.verify_key', 'akismet.Akismet.verify_key', (['self.api_key', 'self.blog_url'], {}), '(self.api_key, self.blog_url)\n', (6024, 6053), False, 'import akismet\n')]