import unittest
from biicode.server.rest.bottle_plugins.massive_error_blocker_bottle_plugin import MassiveErrorBlockerBottlePlugin
from bottle import HTTPResponse, request
from datetime import timedelta
from mock import Mock
import time
from biicode.test.fake_mem_store import FakeMemStore


class MassiveErrorBlockerTest(unittest.TestCase):
    """
    """

    def setUp(self):
        self.ip = "84.34.13.122"
        self.login_ok = True
        self.memory = FakeMemStore()
        self.callback = Mock(wraps=self._callback)
        request.environ['REMOTE_ADDR'] = self.ip

    def tearDown(self):
        pass

    def testOtherExceptionIsRaised(self):
        '''if its not a auth problem, we raise it'''
        self.plugin = MassiveErrorBlockerBottlePlugin(self.memory,
                                                      delta=timedelta(seconds=0.1),
                                                      max_events=100,
                                                      bantime=timedelta(seconds=0.2))
        self.assertRaises(KeyError, self.plugin.apply(self._callback_raiser, None))

    def testCallbackCalled(self):
        self.plugin = MassiveErrorBlockerBottlePlugin(self.memory,
                                                      delta=timedelta(seconds=0.1),
                                                      max_events=100,
                                                      bantime=timedelta(seconds=0.2))
        self.plugin.apply(self.callback, None)()
        self.callback.assert_any_call()
        self.assertEquals(len(self.memory.memory), 0)

    def testMaxMinusOneAttemptsAllowed(self):
        self.plugin = MassiveErrorBlockerBottlePlugin(self.memory,
                                                      delta=timedelta(seconds=0.1),
                                                      max_events=10,
                                                      bantime=timedelta(seconds=0.2))
        self.login_ok = False
        for i in xrange(9):  # 0 to 8 failures
            self.assertRaises(HTTPResponse, self.plugin.apply(self.callback, None), "hello")
            self.assertEqual(i + 1, self.plugin.ip_count(self.ip))
            self.callback.assert_called_with("hello")

        self.login_ok = True
        self.plugin.apply(self.callback, None)("hello2")
        self.callback.assert_called_with("hello2")  # If called, its not blocked

    def testMaxAttemptsBlocked(self):
        self.plugin = MassiveErrorBlockerBottlePlugin(self.memory,
                                                      delta=timedelta(seconds=0.1),
                                                      max_events=10,
                                                      bantime=timedelta(seconds=0.2))
        self.login_ok = False
        for i in xrange(10):  # 0 to 9 failures
            self.assertRaises(HTTPResponse, self.plugin.apply(self.callback, None), "hello")
            self.assertEqual(i + 1, self.plugin.ip_count(self.ip))
            self.callback.assert_called_with("hello")

        self.login_ok = True
        self.assertRaises(HTTPResponse, self.plugin.apply(self.callback, None), "hello2")  # Blocked
        try:
            self.plugin.apply(self.callback, None)("hello2")
        except HTTPResponse, e:
            self.assertIn("Banned", e.body)

    def testCounterExpired(self):
        self.plugin = MassiveErrorBlockerBottlePlugin(self.memory,
                                                      delta=timedelta(seconds=0.1),
                                                      max_events=10,
                                                      bantime=timedelta(seconds=0.2))
        self.login_ok = False
        for i in xrange(9):  # 0 to 8 failures, 1 more left to ban
            self.assertRaises(HTTPResponse, self.plugin.apply(self.callback, None), "hello")
            self.assertEqual(i + 1, self.plugin.ip_count(self.ip))
            self.callback.assert_called_with("hello")

        # Wait delta time and make another fail
        time.sleep(0.11)
        self.assertRaises(HTTPResponse, self.plugin.apply(self.callback, None), "hello")
        self.assertEqual(1, self.plugin.ip_count(self.ip))

        self.login_ok = True
        self.plugin.apply(self.callback, None)("hello2")
        self.assertEquals(len(self.memory.memory), 0)

    def testBanExpired(self):
        self.plugin = MassiveErrorBlockerBottlePlugin(self.memory,
                                                      delta=timedelta(seconds=0.1),
                                                      max_events=10,
                                                      bantime=timedelta(seconds=0.2))
        self.login_ok = False
        for i in xrange(13):  # 0 to 12 failures, banned
            self.assertRaises(HTTPResponse, self.plugin.apply(self.callback, None), "hello")

        # Wait delta time and make another fail, check keep banned
        time.sleep(0.1)
        self.login_ok = True
        self.assertRaises(HTTPResponse, self.plugin.apply(self.callback, None), "hello")
        try:
            self.plugin.apply(self.callback, None)("hello2")
        except HTTPResponse, e:
            self.assertIn("Banned", e.body)

        # Wait to bantime and check again
        time.sleep(0.11)  # 0.1 + 0.1 >= Bantime
        self.plugin.apply(self.callback, None)("hello2")
        self.assertEquals(len(self.memory.memory), 0)

    def _callback(self, *args, **kwargs):
        if not self.login_ok:
            raise HTTPResponse("'Banned'",
                               "401 Unauthorized",
                               {"WWW-Authenticate": 'Basic realm="Login Required"'})
        return HTTPResponse("OK", "200 OK", {})

    def _callback_raiser(self, *args, **kwargs):
        raise KeyError()