# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
Tests dealing with HTTP rate-limiting.
"""

import httplib
import json
import StringIO
import stubout
import time
import unittest
import webob

from xml.dom.minidom import parseString

from nova.api.openstack import limits


TEST_LIMITS = [
    limits.Limit("GET", "/delayed", "^/delayed", 1, limits.PER_MINUTE),
    limits.Limit("POST", "*", ".*", 7, limits.PER_MINUTE),
    limits.Limit("POST", "/servers", "^/servers", 3, limits.PER_MINUTE),
    limits.Limit("PUT", "*", "", 10, limits.PER_MINUTE),
    limits.Limit("PUT", "/servers", "^/servers", 5, limits.PER_MINUTE),
]


class BaseLimitTestSuite(unittest.TestCase):
    """Base test suite which provides relevant stubs and time abstraction."""

    def setUp(self):
        """Run before each test."""
        self.time = 0.0
        self.stubs = stubout.StubOutForTesting()
        self.stubs.Set(limits.Limit, "_get_time", self._get_time)

    def tearDown(self):
        """Run after each test."""
        self.stubs.UnsetAll()

    def _get_time(self):
        """Return the "time" according to this test suite."""
        return self.time


class LimitsControllerV10Test(BaseLimitTestSuite):
    """
    Tests for `limits.LimitsControllerV10` class.
    """

    def setUp(self):
        """Run before each test."""
        BaseLimitTestSuite.setUp(self)
        self.controller = limits.LimitsControllerV10()

    def _get_index_request(self, accept_header="application/json"):
        """Helper to set routing arguments."""
        request = webob.Request.blank("/")
        request.accept = accept_header
        request.environ["wsgiorg.routing_args"] = (None, {
            "action": "index",
            "controller": "",
        })
        return request

    def _populate_limits(self, request):
        """Put limit info into a request."""
        _limits = [
            limits.Limit("GET", "*", ".*", 10, 60).display(),
            limits.Limit("POST", "*", ".*", 5, 60 * 60).display(),
        ]
        request.environ["nova.limits"] = _limits
        return request

    def test_empty_index_json(self):
        """Test getting empty limit details in JSON."""
        request = self._get_index_request()
        response = request.get_response(self.controller)
        expected = {
            "limits": {
                "rate": [],
                "absolute": {},
            },
        }
        body = json.loads(response.body)
        self.assertEqual(expected, body)

    def test_index_json(self):
        """Test getting limit details in JSON."""
        request = self._get_index_request()
        request = self._populate_limits(request)
        response = request.get_response(self.controller)
        expected = {
            "limits": {
                "rate": [{
                    "regex": ".*",
                    "resetTime": 0,
                    "URI": "*",
                    "value": 10,
                    "verb": "GET",
                    "remaining": 10,
                    "unit": "MINUTE",
                },
                {
                    "regex": ".*",
                    "resetTime": 0,
                    "URI": "*",
                    "value": 5,
                    "verb": "POST",
                    "remaining": 5,
                    "unit": "HOUR",
                }],
                "absolute": {},
            },
        }
        body = json.loads(response.body)
        self.assertEqual(expected, body)

    def test_empty_index_xml(self):
        """Test getting limit details in XML."""
        request = self._get_index_request("application/xml")
        response = request.get_response(self.controller)

        expected = parseString("""
            <limits
                xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
                <rate/>
                <absolute/>
            </limits>
        """.replace("  ", ""))

        body = parseString(response.body.replace("  ", ""))

        self.assertEqual(expected.toxml(), body.toxml())

    def test_index_xml(self):
        """Test getting limit details in XML."""
        request = self._get_index_request("application/xml")
        request = self._populate_limits(request)
        response = request.get_response(self.controller)

        expected = parseString("""
            <limits
                xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
                <rate>
                    <limit URI="*" regex=".*" remaining="10" resetTime="0"
                        unit="MINUTE" value="10" verb="GET"/>
                    <limit URI="*" regex=".*" remaining="5" resetTime="0"
                        unit="HOUR" value="5" verb="POST"/>
                </rate>
                <absolute/>
            </limits>
        """.replace("  ", ""))
        body = parseString(response.body.replace("  ", ""))

        self.assertEqual(expected.toxml(), body.toxml())


class LimitsControllerV11Test(BaseLimitTestSuite):
    """
    Tests for `limits.LimitsControllerV11` class.
    """

    def setUp(self):
        """Run before each test."""
        BaseLimitTestSuite.setUp(self)
        self.controller = limits.LimitsControllerV11()

    def _get_index_request(self, accept_header="application/json"):
        """Helper to set routing arguments."""
        request = webob.Request.blank("/")
        request.accept = accept_header
        request.environ["wsgiorg.routing_args"] = (None, {
            "action": "index",
            "controller": "",
        })
        return request

    def _populate_limits(self, request):
        """Put limit info into a request."""
        _limits = [
            limits.Limit("GET", "*", ".*", 10, 60).display(),
            limits.Limit("POST", "*", ".*", 5, 60 * 60).display(),
            limits.Limit("GET", "changes-since*", "changes-since",
                         5, 60).display(),
        ]
        request.environ["nova.limits"] = _limits
        return request

    def test_empty_index_json(self):
        """Test getting empty limit details in JSON."""
        request = self._get_index_request()
        response = request.get_response(self.controller)
        expected = {
            "limits": {
                "rate": [],
                "absolute": {},
            },
        }
        body = json.loads(response.body)
        self.assertEqual(expected, body)

    def test_index_json(self):
        """Test getting limit details in JSON."""
        request = self._get_index_request()
        request = self._populate_limits(request)
        response = request.get_response(self.controller)
        expected = {
            "limits": {
                "rate": [
                    {
                        "regex": ".*",
                        "uri": "*",
                        "limit": [
                            {
                                "verb": "GET",
                                "next-available": 0,
                                "unit": "MINUTE",
                                "value": 10,
                                "remaining": 10,
                            },
                            {
                                "verb": "POST",
                                "next-available": 0,
                                "unit": "HOUR",
                                "value": 5,
                                "remaining": 5,
                            },
                        ],
                    },
                    {
                        "regex": "changes-since",
                        "uri": "changes-since*",
                        "limit": [
                            {
                                "verb": "GET",
                                "next-available": 0,
                                "unit": "MINUTE",
                                "value": 5,
                                "remaining": 5,
                            },
                        ],
                    },

                ],
                "absolute": {},
            },
        }
        body = json.loads(response.body)
        self.assertEqual(expected, body)


class LimitMiddlewareTest(BaseLimitTestSuite):
    """
    Tests for the `limits.RateLimitingMiddleware` class.
    """

    @webob.dec.wsgify
    def _empty_app(self, request):
        """Do-nothing WSGI app."""
        pass

    def setUp(self):