# -*- coding: utf-8 -*-

from json import dumps, loads
import unittest
import struct
import sys
import warnings

from six import b as b_, PY3
from webtest import TestApp

from pecan import abort, expose, make_app, response, redirect
from pecan.rest import RestController
from pecan.tests import PecanTestCase


class TestRestController(PecanTestCase):

    def test_basic_rest(self):

        class OthersController(object):

            @expose()
            def index(self):
                return 'OTHERS'

            @expose()
            def echo(self, value):
                return str(value)

        class ThingsController(RestController):
            data = ['zero', 'one', 'two', 'three']

            _custom_actions = {'count': ['GET'], 'length': ['GET', 'POST']}

            others = OthersController()

            @expose()
            def get_one(self, id):
                return self.data[int(id)]

            @expose('json')
            def get_all(self):
                return dict(items=self.data)

            @expose()
            def length(self, id, value=None):
                length = len(self.data[int(id)])
                if value:
                    length += len(value)
                return str(length)

            @expose()
            def get_count(self):
                return str(len(self.data))

            @expose()
            def new(self):
                return 'NEW'

            @expose()
            def post(self, value):
                self.data.append(value)
                response.status = 302
                return 'CREATED'

            @expose()
            def edit(self, id):
                return 'EDIT %s' % self.data[int(id)]

            @expose()
            def put(self, id, value):
                self.data[int(id)] = value
                return 'UPDATED'

            @expose()
            def get_delete(self, id):
                return 'DELETE %s' % self.data[int(id)]

            @expose()
            def delete(self, id):
                del self.data[int(id)]
                return 'DELETED'

            @expose()
            def reset(self):
                return 'RESET'

            @expose()
            def post_options(self):
                return 'OPTIONS'

            @expose()
            def options(self):
                abort(500)

            @expose()
            def other(self):
                abort(500)

        class RootController(object):
            things = ThingsController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test get_all
        r = app.get('/things')
        assert r.status_int == 200
        assert r.body == b_(dumps(dict(items=ThingsController.data)))

        # test get_one
        for i, value in enumerate(ThingsController.data):
            r = app.get('/things/%d' % i)
            assert r.status_int == 200
            assert r.body == b_(value)

        # test post
        r = app.post('/things', {'value': 'four'})
        assert r.status_int == 302
        assert r.body == b_('CREATED')

        # make sure it works
        r = app.get('/things/4')
        assert r.status_int == 200
        assert r.body == b_('four')

        # test edit
        r = app.get('/things/3/edit')
        assert r.status_int == 200
        assert r.body == b_('EDIT three')

        # test put
        r = app.put('/things/4', {'value': 'FOUR'})
        assert r.status_int == 200
        assert r.body == b_('UPDATED')

        # make sure it works
        r = app.get('/things/4')
        assert r.status_int == 200
        assert r.body == b_('FOUR')

        # test put with _method parameter and GET
        r = app.get('/things/4?_method=put', {'value': 'FOUR!'}, status=405)
        assert r.status_int == 405

        # make sure it works
        r = app.get('/things/4')
        assert r.status_int == 200
        assert r.body == b_('FOUR')

        # test put with _method parameter and POST
        r = app.post('/things/4?_method=put', {'value': 'FOUR!'})
        assert r.status_int == 200
        assert r.body == b_('UPDATED')

        # make sure it works
        r = app.get('/things/4')
        assert r.status_int == 200
        assert r.body == b_('FOUR!')

        # test get delete
        r = app.get('/things/4/delete')
        assert r.status_int == 200
        assert r.body == b_('DELETE FOUR!')

        # test delete
        r = app.delete('/things/4')
        assert r.status_int == 200
        assert r.body == b_('DELETED')

        # make sure it works
        r = app.get('/things')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 4

        # test delete with _method parameter and GET
        r = app.get('/things/3?_method=DELETE', status=405)
        assert r.status_int == 405

        # make sure it works
        r = app.get('/things')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 4

        # test delete with _method parameter and POST
        r = app.post('/things/3?_method=DELETE')
        assert r.status_int == 200
        assert r.body == b_('DELETED')

        # make sure it works
        r = app.get('/things')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 3

        # test "RESET" custom action
        r = app.request('/things', method='RESET')
        assert r.status_int == 200
        assert r.body == b_('RESET')

        # test "RESET" custom action with _method parameter
        r = app.get('/things?_method=RESET')
        assert r.status_int == 200
        assert r.body == b_('RESET')

        # test the "OPTIONS" custom action
        r = app.request('/things', method='OPTIONS')
        assert r.status_int == 200
        assert r.body == b_('OPTIONS')

        # test the "OPTIONS" custom action with the _method parameter
        r = app.post('/things', {'_method': 'OPTIONS'})
        assert r.status_int == 200
        assert r.body == b_('OPTIONS')

        # test the "other" custom action
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            r = app.request('/things/other', method='MISC', status=405)
            assert r.status_int == 405

        # test the "other" custom action with the _method parameter
        r = app.post('/things/other', {'_method': 'MISC'}, status=405)
        assert r.status_int == 405

        # test the "others" custom action
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            r = app.request('/things/others/', method='MISC')
            assert r.status_int == 200
            assert r.body == b_('OTHERS')

        # test the "others" custom action missing trailing slash
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            r = app.request('/things/others', method='MISC', status=302)
            assert r.status_int == 302

        # test the "others" custom action with the _method parameter
        r = app.get('/things/others/?_method=MISC')
        assert r.status_int == 200
        assert r.body == b_('OTHERS')

        # test an invalid custom action
        r = app.get('/things?_method=BAD', status=405)
        assert r.status_int == 405

        # test custom "GET" request "count"
        r = app.get('/things/count')
        assert r.status_int == 200
        assert r.body == b_('3')

        # test custom "GET" request "length"
        r = app.get('/things/1/length')
        assert r.status_int == 200
        assert r.body == b_(str(len('one')))

        # test custom "GET" request through subcontroller
        r = app.get('/things/others/echo?value=test')
        assert r.status_int == 200
        assert r.body == b_('test')

        # test custom "POST" request "length"
        r = app.post('/things/1/length', {'value': 'test'})
        assert r.status_int == 200
        assert r.body == b_(str(len('onetest')))

        # test custom "POST" request through subcontroller
        r = app.post('/things/others/echo', {'value': 'test'})
        assert r.status_int == 200
        assert r.body == b_('test')

    def test_getall_with_trailing_slash(self):

        class ThingsController(RestController):

            data = ['zero', 'one', 'two', 'three']

            @expose('json')
            def get_all(self):
                return dict(items=self.data)

        class RootController(object):
            things = ThingsController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test get_all
        r = app.get('/things/')
        assert r.status_int == 200
        assert r.body == b_(dumps(dict(items=ThingsController.data)))

    def test_405_with_lookup(self):

        class LookupController(RestController):

            def __init__(self, _id):
                self._id = _id

            @expose()
            def get_all(self):
                return 'ID: %s' % self._id

        class ThingsController(RestController):

            @expose()
            def _lookup(self, _id, *remainder):
                return LookupController(_id), remainder

        class RootController(object):
            things = ThingsController()

        # create the app
        app = TestApp(make_app(RootController()))

        # these should 405
        for path in ('/things', '/things/'):
            r = app.get(path, expect_errors=True)
            assert r.status_int == 405

        r = app.get('/things/foo')
        assert r.status_int == 200
        assert r.body == b_('ID: foo')

    def test_getall_with_lookup(self):

        class LookupController(RestController):

            def __init__(self, _id):
                self._id = _id

            @expose()
            def get_all(self):
                return 'ID: %s' % self._id

        class ThingsController(RestController):

            data = ['zero', 'one', 'two', 'three']

            @expose()
            def _lookup(self, _id, *remainder):
                return LookupController(_id), remainder

            @expose('json')
            def get_all(self):
                return dict(items=self.data)

        class RootController(object):
            things = ThingsController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test get_all
        for path in ('/things', '/things/'):
            r = app.get(path)
            assert r.status_int == 200
            assert r.body == b_(dumps(dict(items=ThingsController.data)))

        r = app.get('/things/foo')
        assert r.status_int == 200
        assert r.body == b_('ID: foo')

    def test_simple_nested_rest(self):

        class BarController(RestController):

            @expose()
            def post(self):
                return "BAR-POST"

            @expose()
            def delete(self, id_):
                return "BAR-%s" % id_

        class FooController(RestController):

            bar = BarController()

            @expose()
            def post(self):
                return "FOO-POST"

            @expose()
            def delete(self, id_):
                return "FOO-%s" % id_

        class RootController(object):
            foo = FooController()

        # create the app
        app = TestApp(make_app(RootController()))

        r = app.post('/foo')
        assert r.status_int == 200
        assert r.body == b_("FOO-POST")

        r = app.delete('/foo/1')
        assert r.status_int == 200
        assert r.body == b_("FOO-1")

        r = app.post('/foo/bar')
        assert r.status_int == 200
        assert r.body == b_("BAR-POST")

        r = app.delete('/foo/bar/2')
        assert r.status_int == 200
        assert r.body == b_("BAR-2")

    def test_complicated_nested_rest(self):

        class BarsController(RestController):

            data = [['zero-zero', 'zero-one'], ['one-zero', 'one-one']]

            @expose()
            def get_one(self, foo_id, id):
                return self.data[int(foo_id)][int(id)]

            @expose('json')
            def get_all(self, foo_id):
                return dict(items=self.data[int(foo_id)])

            @expose()
            def new(self, foo_id):
                return 'NEW FOR %s' % foo_id

            @expose()
            def post(self, foo_id, value):
                foo_id = int(foo_id)
                if len(self.data) < foo_id + 1:
                    self.data.extend([[]] * (foo_id - len(self.data) + 1))
                self.data[foo_id].append(value)
                response.status = 302
                return 'CREATED FOR %s' % foo_id

            @expose()
            def edit(self, foo_id, id):
                return 'EDIT %s' % self.data[int(foo_id)][int(id)]

            @expose()
            def put(self, foo_id, id, value):
                self.data[int(foo_id)][int(id)] = value
                return 'UPDATED'

            @expose()
            def get_delete(self, foo_id, id):
                return 'DELETE %s' % self.data[int(foo_id)][int(id)]

            @expose()
            def delete(self, foo_id, id):
                del self.data[int(foo_id)][int(id)]
                return 'DELETED'

        class FoosController(RestController):

            data = ['zero', 'one']

            bars = BarsController()

            @expose()
            def get_one(self, id):
                return self.data[int(id)]

            @expose('json')
            def get_all(self):
                return dict(items=self.data)

            @expose()
            def new(self):
                return 'NEW'

            @expose()
            def edit(self, id):
                return 'EDIT %s' % self.data[int(id)]

            @expose()
            def post(self, value):
                self.data.append(value)
                response.status = 302
                return 'CREATED'

            @expose()
            def put(self, id, value):
                self.data[int(id)] = value
                return 'UPDATED'

            @expose()
            def get_delete(self, id):
                return 'DELETE %s' % self.data[int(id)]

            @expose()
            def delete(self, id):
                del self.data[int(id)]
                return 'DELETED'

        class RootController(object):
            foos = FoosController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test get_all
        r = app.get('/foos')
        assert r.status_int == 200
        assert r.body == b_(dumps(dict(items=FoosController.data)))

        # test nested get_all
        r = app.get('/foos/1/bars')
        assert r.status_int == 200
        assert r.body == b_(dumps(dict(items=BarsController.data[1])))

        # test get_one
        for i, value in enumerate(FoosController.data):
            r = app.get('/foos/%d' % i)
            assert r.status_int == 200
            assert r.body == b_(value)

        # test nested get_one
        for i, value in enumerate(FoosController.data):
            for j, value in enumerate(BarsController.data[i]):
                r = app.get('/foos/%s/bars/%s' % (i, j))
                assert r.status_int == 200
                assert r.body == b_(value)

        # test post
        r = app.post('/foos', {'value': 'two'})
        assert r.status_int == 302
        assert r.body == b_('CREATED')

        # make sure it works
        r = app.get('/foos/2')
        assert r.status_int == 200
        assert r.body == b_('two')

        # test nested post
        r = app.post('/foos/2/bars', {'value': 'two-zero'})
        assert r.status_int == 302
        assert r.body == b_('CREATED FOR 2')

        # make sure it works
        r = app.get('/foos/2/bars/0')
        assert r.status_int == 200
        assert r.body == b_('two-zero')

        # test edit
        r = app.get('/foos/1/edit')
        assert r.status_int == 200
        assert r.body == b_('EDIT one')

        # test nested edit
        r = app.get('/foos/1/bars/1/edit')
        assert r.status_int == 200
        assert r.body == b_('EDIT one-one')

        # test put
        r = app.put('/foos/2', {'value': 'TWO'})
        assert r.status_int == 200
        assert r.body == b_('UPDATED')

        # make sure it works
        r = app.get('/foos/2')
        assert r.status_int == 200
        assert r.body == b_('TWO')

        # test nested put
        r = app.put('/foos/2/bars/0', {'value': 'TWO-ZERO'})
        assert r.status_int == 200
        assert r.body == b_('UPDATED')

        # make sure it works
        r = app.get('/foos/2/bars/0')
        assert r.status_int == 200
        assert r.body == b_('TWO-ZERO')

        # test put with _method parameter and GET
        r = app.get('/foos/2?_method=put', {'value': 'TWO!'}, status=405)
        assert r.status_int == 405

        # make sure it works
        r = app.get('/foos/2')
        assert r.status_int == 200
        assert r.body == b_('TWO')

        # test nested put with _method parameter and GET
        r = app.get(
            '/foos/2/bars/0?_method=put',
            {'value': 'ZERO-TWO!'}, status=405
        )
        assert r.status_int == 405

        # make sure it works
        r = app.get('/foos/2/bars/0')
        assert r.status_int == 200
        assert r.body == b_('TWO-ZERO')

        # test put with _method parameter and POST
        r = app.post('/foos/2?_method=put', {'value': 'TWO!'})
        assert r.status_int == 200
        assert r.body == b_('UPDATED')

        # make sure it works
        r = app.get('/foos/2')
        assert r.status_int == 200
        assert r.body == b_('TWO!')

        # test nested put with _method parameter and POST
        r = app.post('/foos/2/bars/0?_method=put', {'value': 'TWO-ZERO!'})
        assert r.status_int == 200
        assert r.body == b_('UPDATED')

        # make sure it works
        r = app.get('/foos/2/bars/0')
        assert r.status_int == 200
        assert r.body == b_('TWO-ZERO!')

        # test get delete
        r = app.get('/foos/2/delete')
        assert r.status_int == 200
        assert r.body == b_('DELETE TWO!')

        # test nested get delete
        r = app.get('/foos/2/bars/0/delete')
        assert r.status_int == 200
        assert r.body == b_('DELETE TWO-ZERO!')

        # test nested delete
        r = app.delete('/foos/2/bars/0')
        assert r.status_int == 200
        assert r.body == b_('DELETED')

        # make sure it works
        r = app.get('/foos/2/bars')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 0

        # test delete
        r = app.delete('/foos/2')
        assert r.status_int == 200
        assert r.body == b_('DELETED')

        # make sure it works
        r = app.get('/foos')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 2

        # test nested delete with _method parameter and GET
        r = app.get('/foos/1/bars/1?_method=DELETE', status=405)
        assert r.status_int == 405

        # make sure it works
        r = app.get('/foos/1/bars')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 2

        # test delete with _method parameter and GET
        r = app.get('/foos/1?_method=DELETE', status=405)
        assert r.status_int == 405

        # make sure it works
        r = app.get('/foos')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 2

        # test nested delete with _method parameter and POST
        r = app.post('/foos/1/bars/1?_method=DELETE')
        assert r.status_int == 200
        assert r.body == b_('DELETED')

        # make sure it works
        r = app.get('/foos/1/bars')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 1

        # test delete with _method parameter and POST
        r = app.post('/foos/1?_method=DELETE')
        assert r.status_int == 200
        assert r.body == b_('DELETED')

        # make sure it works
        r = app.get('/foos')
        assert r.status_int == 200
        assert len(loads(r.body.decode())['items']) == 1

    def test_nested_get_all(self):

        class BarsController(RestController):

            @expose()
            def get_one(self, foo_id, id):
                return '4'

            @expose()
            def get_all(self, foo_id):
                return '3'

        class FoosController(RestController):

            bars = BarsController()

            @expose()
            def get_one(self, id):
                return '2'

            @expose()
            def get_all(self):
                return '1'

        class RootController(object):
            foos = FoosController()

        # create the app
        app = TestApp(make_app(RootController()))

        r = app.get('/foos/')
        assert r.status_int == 200
        assert r.body == b_('1')

        r = app.get('/foos/1/')
        assert r.status_int == 200
        assert r.body == b_('2')

        r = app.get('/foos/1/bars/')
        assert r.status_int == 200
        assert r.body == b_('3')

        r = app.get('/foos/1/bars/2/')
        assert r.status_int == 200
        assert r.body == b_('4')

        r = app.get('/foos/bars/', status=404)
        assert r.status_int == 404

        r = app.get('/foos/bars/1', status=404)
        assert r.status_int == 404

    def test_nested_get_all_with_lookup(self):

        class BarsController(RestController):

            @expose()
            def get_one(self, foo_id, id):
                return '4'

            @expose()
            def get_all(self, foo_id):
                return '3'

            @expose('json')
            def _lookup(self, id, *remainder):
                redirect('/lookup-hit/')

        class FoosController(RestController):

            bars = BarsController()

            @expose()
            def get_one(self, id):
                return '2'

            @expose()
            def get_all(self):
                return '1'

        class RootController(object):
            foos = FoosController()

        # create the app
        app = TestApp(make_app(RootController()))

        r = app.get('/foos/')
        assert r.status_int == 200
        assert r.body == b_('1')

        r = app.get('/foos/1/')
        assert r.status_int == 200
        assert r.body == b_('2')

        r = app.get('/foos/1/bars/')
        assert r.status_int == 200
        assert r.body == b_('3')

        r = app.get('/foos/1/bars/2/')
        assert r.status_int == 200
        assert r.body == b_('4')

        r = app.get('/foos/bars/')
        assert r.status_int == 302
        assert r.headers['Location'].endswith('/lookup-hit/')

        r = app.get('/foos/bars/1')
        assert r.status_int == 302
        assert r.headers['Location'].endswith('/lookup-hit/')

    def test_bad_rest(self):

        class ThingsController(RestController):
            pass

        class RootController(object):
            things = ThingsController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test get_all
        r = app.get('/things', status=405)
        assert r.status_int == 405

        # test get_one
        r = app.get('/things/1', status=405)
        assert r.status_int == 405

        # test post
        r = app.post('/things', {'value': 'one'}, status=405)
        assert r.status_int == 405

        # test edit
        r = app.get('/things/1/edit', status=405)
        assert r.status_int == 405

        # test put
        r = app.put('/things/1', {'value': 'ONE'}, status=405)

        # test put with _method parameter and GET
        r = app.get('/things/1?_method=put', {'value': 'ONE!'}, status=405)
        assert r.status_int == 405

        # test put with _method parameter and POST
        r = app.post('/things/1?_method=put', {'value': 'ONE!'}, status=405)
        assert r.status_int == 405

        # test get delete
        r = app.get('/things/1/delete', status=405)
        assert r.status_int == 405

        # test delete
        r = app.delete('/things/1', status=405)
        assert r.status_int == 405

        # test delete with _method parameter and GET
        r = app.get('/things/1?_method=DELETE', status=405)
        assert r.status_int == 405

        # test delete with _method parameter and POST
        r = app.post('/things/1?_method=DELETE', status=405)
        assert r.status_int == 405

        # test "RESET" custom action
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            r = app.request('/things', method='RESET', status=405)
            assert r.status_int == 405

    def test_nested_rest_with_missing_intermediate_id(self):

        class BarsController(RestController):

            data = [['zero-zero', 'zero-one'], ['one-zero', 'one-one']]

            @expose('json')
            def get_all(self, foo_id):
                return dict(items=self.data[int(foo_id)])

        class FoosController(RestController):

            data = ['zero', 'one']

            bars = BarsController()

            @expose()
            def get_one(self, id):
                return self.data[int(id)]

            @expose('json')
            def get_all(self):
                return dict(items=self.data)

        class RootController(object):
            foos = FoosController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test get_all
        r = app.get('/foos')
        self.assertEqual(r.status_int, 200)
        self.assertEqual(r.body, b_(dumps(dict(items=FoosController.data))))

        # test nested get_all
        r = app.get('/foos/1/bars')
        self.assertEqual(r.status_int, 200)
        self.assertEqual(r.body, b_(dumps(dict(items=BarsController.data[1]))))

        r = app.get('/foos/bars', expect_errors=True)
        self.assertEqual(r.status_int, 404)

    def test_custom_with_trailing_slash(self):

        class CustomController(RestController):

            _custom_actions = {
                'detail': ['GET'],
                'create': ['POST'],
                'update': ['PUT'],
                'remove': ['DELETE'],
            }

            @expose()
            def detail(self):
                return 'DETAIL'

            @expose()
            def create(self):
                return 'CREATE'

            @expose()
            def update(self, id):
                return id

            @expose()
            def remove(self, id):
                return id

        app = TestApp(make_app(CustomController()))

        r = app.get('/detail')
        assert r.status_int == 200
        assert r.body == b_('DETAIL')

        r = app.get('/detail/')
        assert r.status_int == 200
        assert r.body == b_('DETAIL')

        r = app.post('/create')
        assert r.status_int == 200
        assert r.body == b_('CREATE')

        r = app.post('/create/')
        assert r.status_int == 200
        assert r.body == b_('CREATE')

        r = app.put('/update/123')
        assert r.status_int == 200
        assert r.body == b_('123')

        r = app.put('/update/123/')
        assert r.status_int == 200
        assert r.body == b_('123')

        r = app.delete('/remove/456')
        assert r.status_int == 200
        assert r.body == b_('456')

        r = app.delete('/remove/456/')
        assert r.status_int == 200
        assert r.body == b_('456')

    def test_custom_delete(self):

        class OthersController(object):

            @expose()
            def index(self):
                return 'DELETE'

            @expose()
            def reset(self, id):
                return str(id)

        class ThingsController(RestController):

            others = OthersController()

            @expose()
            def delete_fail(self):
                abort(500)

        class RootController(object):
            things = ThingsController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test bad delete
        r = app.delete('/things/delete_fail', status=405)
        assert r.status_int == 405

        # test bad delete with _method parameter and GET
        r = app.get('/things/delete_fail?_method=delete', status=405)
        assert r.status_int == 405

        # test bad delete with _method parameter and POST
        r = app.post('/things/delete_fail', {'_method': 'delete'}, status=405)
        assert r.status_int == 405

        # test custom delete without ID
        r = app.delete('/things/others/')
        assert r.status_int == 200
        assert r.body == b_('DELETE')

        # test custom delete without ID with _method parameter and GET
        r = app.get('/things/others/?_method=delete', status=405)
        assert r.status_int == 405

        # test custom delete without ID with _method parameter and POST
        r = app.post('/things/others/', {'_method': 'delete'})
        assert r.status_int == 200
        assert r.body == b_('DELETE')

        # test custom delete with ID
        r = app.delete('/things/others/reset/1')
        assert r.status_int == 200
        assert r.body == b_('1')

        # test custom delete with ID with _method parameter and GET
        r = app.get('/things/others/reset/1?_method=delete', status=405)
        assert r.status_int == 405

        # test custom delete with ID with _method parameter and POST
        r = app.post('/things/others/reset/1', {'_method': 'delete'})
        assert r.status_int == 200
        assert r.body == b_('1')

    def test_get_with_var_args(self):

        class OthersController(object):

            @expose()
            def index(self, one, two, three):
                return 'NESTED: %s, %s, %s' % (one, two, three)

        class ThingsController(RestController):

            others = OthersController()

            @expose()
            def get_one(self, *args):
                return ', '.join(args)

        class RootController(object):
            things = ThingsController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test get request
        r = app.get('/things/one/two/three')
        assert r.status_int == 200
        assert r.body == b_('one, two, three')

        # test nested get request
        r = app.get('/things/one/two/three/others/')
        assert r.status_int == 200
        assert r.body == b_('NESTED: one, two, three')

    def test_sub_nested_rest(self):

        class BazsController(RestController):

            data = [[['zero-zero-zero']]]

            @expose()
            def get_one(self, foo_id, bar_id, id):
                return self.data[int(foo_id)][int(bar_id)][int(id)]

        class BarsController(RestController):

            data = [['zero-zero']]

            bazs = BazsController()

            @expose()
            def get_one(self, foo_id, id):
                return self.data[int(foo_id)][int(id)]

        class FoosController(RestController):

            data = ['zero']

            bars = BarsController()

            @expose()
            def get_one(self, id):
                return self.data[int(id)]

        class RootController(object):
            foos = FoosController()

        # create the app
        app = TestApp(make_app(RootController()))

        # test sub-nested get_one
        r = app.get('/foos/0/bars/0/bazs/0')
        assert r.status_int == 200
        assert r.body == b_('zero-zero-zero')

    def test_sub_nested_rest_with_overwrites(self):

        class FinalController(object):

            @expose()
            def index(self):
                return 'FINAL'

            @expose()
            def named(self):
                return 'NAMED'

        class BazsController(RestController):

            data = [[['zero-zero-zero']]]

            final = FinalController()

            @expose()
            def get_one(self, foo_id, bar_id, id):
                return self.data[int(foo_id)][int(bar_id)][int(id)]

            @expose()
            def post(self):
                return 'POST-GRAND-CHILD'

            @expose()
            def put(self, id):
                return 'PUT-GRAND-CHILD'

        class BarsController(RestController):

            data = [['zero-zero']]

            bazs = BazsController()

            @expose()
            def get_one(self, foo_id, id):
                return self.data[int(foo_id)][int(id)]

            @expose()
            def post(self):
                return 'POST-CHILD'

            @expose()
            def put(self, id):
                return 'PUT-CHILD'

        class FoosController(RestController):

            data = ['zero']

            bars = BarsController()

            @expose()
            def get_one(self, id):
                return self.data[int(id)]

            @expose()
            def post(self):
                return 'POST'

            @expose()
            def put(self, id):
                return 'PUT'

        class RootController(object):
            foos = FoosController()

        # create the app
        app = TestApp(make_app(RootController()))

        r = app.post('/foos')
        assert r.status_int == 200
        assert r.body == b_('POST')

        r = app.put('/foos/0')
        assert r.status_int == 200
        assert r.body == b_('PUT')

        r = app.post('/foos/bars')
        assert r.status_int == 200
        assert r.body == b_('POST-CHILD')

        r = app.put('/foos/bars/0')
        assert r.status_int == 200
        assert r.body == b_('PUT-CHILD')

        r = app.post('/foos/bars/bazs')
        assert r.status_int == 200
        assert r.body == b_('POST-GRAND-CHILD')

        r = app.put('/foos/bars/bazs/0')
        assert r.status_int == 200
        assert r.body == b_('PUT-GRAND-CHILD')

        r = app.get('/foos/bars/bazs/final/')
        assert r.status_int == 200
        assert r.body == b_('FINAL')

        r = app.get('/foos/bars/bazs/final/named')
        assert r.status_int == 200
        assert r.body == b_('NAMED')

    def test_post_with_kwargs_only(self):

        class RootController(RestController):

            @expose()
            def get_all(self):
                return 'INDEX'

            @expose('json')
            def post(self, **kw):
                return kw

        # create the app
        app = TestApp(make_app(RootController()))

        r = app.get('/')
        assert r.status_int == 200
        assert r.body == b_('INDEX')

        kwargs = {'foo': 'bar', 'spam': 'eggs'}
        r = app.post('/', kwargs)
        assert r.status_int == 200
        assert r.namespace['foo'] == 'bar'
        assert r.namespace['spam'] == 'eggs'

    def test_nested_rest_with_lookup(self):

        class SubController(RestController):

            @expose()
            def get_all(self):
                return "SUB"

        class FinalController(RestController):

            def __init__(self, id_):
                self.id_ = id_

            @expose()
            def get_all(self):
                return "FINAL-%s" % self.id_

            @expose()
            def post(self):
                return "POST-%s" % self.id_

        class LookupController(RestController):

            sub = SubController()

            def __init__(self, id_):
                self.id_ = id_

            @expose()
            def _lookup(self, id_, *remainder):
                return FinalController(id_), remainder

            @expose()
            def get_all(self):
                raise AssertionError("Never Reached")

            @expose()
            def post(self):
                return "POST-LOOKUP-%s" % self.id_

            @expose()
            def put(self, id_):
                return "PUT-LOOKUP-%s-%s" % (self.id_, id_)

            @expose()
            def delete(self, id_):
                return "DELETE-LOOKUP-%s-%s" % (self.id_, id_)

        class FooController(RestController):

            @expose()
            def _lookup(self, id_, *remainder):
                return LookupController(id_), remainder

            @expose()
            def get_one(self, id_):
                return "GET ONE"

            @expose()
            def get_all(self):
                return "INDEX"

            @expose()
            def post(self):
                return "POST"

            @expose()
            def put(self, id_):
                return "PUT-%s" % id_

            @expose()
            def delete(self, id_):
                return "DELETE-%s" % id_

        class RootController(RestController):
            foo = FooController()

        app = TestApp(make_app(RootController()))

        r = app.get('/foo')
        assert r.status_int == 200
        assert r.body == b_('INDEX')

        r = app.post('/foo')
        assert r.status_int == 200
        assert r.body == b_('POST')

        r = app.get('/foo/1')
        assert r.status_int == 200
        assert r.body == b_('GET ONE')

        r = app.post('/foo/1')
        assert r.status_int == 200
        assert r.body == b_('POST-LOOKUP-1')

        r = app.put('/foo/1')
        assert r.status_int == 200
        assert r.body == b_('PUT-1')

        r = app.delete('/foo/1')
        assert r.status_int == 200
        assert r.body == b_('DELETE-1')

        r = app.put('/foo/1/2')
        assert r.status_int == 200
        assert r.body == b_('PUT-LOOKUP-1-2')

        r = app.delete('/foo/1/2')
        assert r.status_int == 200
        assert r.body == b_('DELETE-LOOKUP-1-2')

        r = app.get('/foo/1/2')
        assert r.status_int == 200
        assert r.body == b_('FINAL-2')

        r = app.post('/foo/1/2')
        assert r.status_int == 200
        assert r.body == b_('POST-2')

    def test_nested_rest_with_default(self):

        class FooController(RestController):

            @expose()
            def _default(self, *remainder):
                return "DEFAULT %s" % remainder

        class RootController(RestController):
            foo = FooController()

        app = TestApp(make_app(RootController()))

        r = app.get('/foo/missing')
        assert r.status_int == 200
        assert r.body == b_("DEFAULT missing")

    def test_rest_with_non_utf_8_body(self):
        if PY3:
            # webob+PY3 doesn't suffer from this bug; the POST parsing in PY3
            # seems to more gracefully detect the bytestring
            return

        class FooController(RestController):

            @expose()
            def post(self):
                return "POST"

        class RootController(RestController):
            foo = FooController()

        app = TestApp(make_app(RootController()))

        data = struct.pack('255h', *range(0, 255))
        r = app.post('/foo/', data, expect_errors=True)
        assert r.status_int == 400

    def test_dynamic_rest_lookup(self):
        class BarController(RestController):
            @expose()
            def get_all(self):
                return "BAR"

            @expose()
            def put(self):
                return "PUT_BAR"

            @expose()
            def delete(self):
                return "DELETE_BAR"

        class BarsController(RestController):
            @expose()
            def _lookup(self, id_, *remainder):
                return BarController(), remainder

            @expose()
            def get_all(self):
                return "BARS"

            @expose()
            def post(self):
                return "POST_BARS"

        class FooController(RestController):
            bars = BarsController()

            @expose()
            def get_all(self):
                return "FOO"

            @expose()
            def put(self):
                return "PUT_FOO"

            @expose()
            def delete(self):
                return "DELETE_FOO"

        class FoosController(RestController):
            @expose()
            def _lookup(self, id_, *remainder):
                return FooController(), remainder

            @expose()
            def get_all(self):
                return "FOOS"

            @expose()
            def post(self):
                return "POST_FOOS"

        class RootController(RestController):
            foos = FoosController()

        app = TestApp(make_app(RootController()))

        r = app.get('/foos')
        assert r.status_int == 200
        assert r.body == b_('FOOS')

        r = app.post('/foos')
        assert r.status_int == 200
        assert r.body == b_('POST_FOOS')

        r = app.get('/foos/foo')
        assert r.status_int == 200
        assert r.body == b_('FOO')

        r = app.put('/foos/foo')
        assert r.status_int == 200
        assert r.body == b_('PUT_FOO')

        r = app.delete('/foos/foo')
        assert r.status_int == 200
        assert r.body == b_('DELETE_FOO')

        r = app.get('/foos/foo/bars')
        assert r.status_int == 200
        assert r.body == b_('BARS')

        r = app.post('/foos/foo/bars')
        assert r.status_int == 200
        assert r.body == b_('POST_BARS')

        r = app.get('/foos/foo/bars/bar')
        assert r.status_int == 200
        assert r.body == b_('BAR')

        r = app.put('/foos/foo/bars/bar')
        assert r.status_int == 200
        assert r.body == b_('PUT_BAR')

        r = app.delete('/foos/foo/bars/bar')
        assert r.status_int == 200
        assert r.body == b_('DELETE_BAR')

    def test_method_not_allowed_get(self):
        class ThingsController(RestController):

            @expose()
            def put(self, id_, value):
                response.status = 200

            @expose()
            def delete(self, id_):
                response.status = 200

        app = TestApp(make_app(ThingsController()))
        r = app.get('/', status=405)
        assert r.status_int == 405
        assert r.headers['Allow'] == 'DELETE, PUT'

    def test_method_not_allowed_post(self):
        class ThingsController(RestController):

            @expose()
            def get_one(self):
                return dict()

        app = TestApp(make_app(ThingsController()))
        r = app.post('/', {'foo': 'bar'}, status=405)
        assert r.status_int == 405
        assert r.headers['Allow'] == 'GET'

    def test_method_not_allowed_put(self):
        class ThingsController(RestController):

            @expose()
            def get_one(self):
                return dict()

        app = TestApp(make_app(ThingsController()))
        r = app.put('/123', status=405)
        assert r.status_int == 405
        assert r.headers['Allow'] == 'GET'

    def test_method_not_allowed_delete(self):
        class ThingsController(RestController):

            @expose()
            def get_one(self):
                return dict()

        app = TestApp(make_app(ThingsController()))
        r = app.delete('/123', status=405)
        assert r.status_int == 405
        assert r.headers['Allow'] == 'GET'

    def test_proper_allow_header_multiple_gets(self):
        class ThingsController(RestController):

            @expose()
            def get_all(self):
                return dict()

            @expose()
            def get(self):
                return dict()

        app = TestApp(make_app(ThingsController()))
        r = app.put('/123', status=405)
        assert r.status_int == 405
        assert r.headers['Allow'] == 'GET'

    @unittest.skipIf(sys.maxunicode <= 65536, 'narrow python build with UCS-2')
    def test_rest_with_utf8_uri(self):

        class FooController(RestController):
            key = chr(0x1F330) if PY3 else unichr(0x1F330)
            data = {key: 'Success!'}

            @expose()
            def get_one(self, id_):
                return self.data[id_]

            @expose()
            def get_all(self):
                return "Hello, World!"

            @expose()
            def put(self, id_, value):
                return self.data[id_]

            @expose()
            def delete(self, id_):
                return self.data[id_]

        class RootController(RestController):
            foo = FooController()

        app = TestApp(make_app(RootController()))

        r = app.get('/foo/%F0%9F%8C%B0')
        assert r.status_int == 200
        assert r.body == b'Success!'

        r = app.put('/foo/%F0%9F%8C%B0', {'value': 'pecans'})
        assert r.status_int == 200
        assert r.body == b'Success!'

        r = app.delete('/foo/%F0%9F%8C%B0')
        assert r.status_int == 200
        assert r.body == b'Success!'

        r = app.get('/foo/')
        assert r.status_int == 200
        assert r.body == b'Hello, World!'

    @unittest.skipIf(not PY3, "test is Python3 specific")
    def test_rest_with_utf8_endpoint(self):
        class ChildController(object):
            @expose()
            def index(self):
                return 'Hello, World!'

        class FooController(RestController):
            pass

        # okay, so it's technically a chestnut, but close enough...
        setattr(FooController, '🌰', ChildController())

        class RootController(RestController):
            foo = FooController()

        app = TestApp(make_app(RootController()))

        r = app.get('/foo/%F0%9F%8C%B0/')
        assert r.status_int == 200
        assert r.body == b'Hello, World!'


class TestExplicitRoute(PecanTestCase):

    def test_alternate_route(self):

        class RootController(RestController):

            @expose(route='some-path')
            def get_all(self):
                return "Hello, World!"

        self.assertRaises(
            ValueError,
            RootController
        )
