def test_decode_reference(self):
        client = Client('http://example.com', fetch_schema=False)

        User = client.resource_factory('user', {
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                }
            },
            "links": [
                {
                    "rel": "self",
                    "method": "GET",
                    "href": "/user/{id}"
                },
                {
                    "rel": "instances",
                    "method": "GET",
                    "href": "/user"
                }
            ]
        })

        result = json.loads(json.dumps({
            "owner": {"$ref": "/user/123"}
        }), cls=PotionJSONDecoder, client=client)

        self.assertEqual({
            "owner": User(123)
        }, result)
    def test_resource_update_property(self):
        client = Client('http://example.com/api', fetch_schema=False)

        User = client.resource_factory('user', {
            "type": "object",
            "description": "The description for 'user'.",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                }
            },
            "links": []
        })

        user = User()
        user.name = 'foo'
        self.assertEqual('foo', user.name)
        self.assertEqual('foo', user['name'])
        self.assertEqual({
            "$uri": None,
            "name": "foo"
        }, user._properties)

        with self.assertRaises(AttributeError):
            user.id = 123
    def test_singleton(self):
        client = Client('http://example.com/api', fetch_schema=False)

        User = client.resource_factory('user', {
            "type": "object",
            "description": "The description for 'user'.",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                }
            },
            "links": [
                {
                    "rel": "self",
                    "href": "/api/user/{id}",
                    "method": "GET"
                }
            ]
        })

        user_a = User('/api/user/123')
        user_b = User('/api/user/123')

        self.assertIs(user_a, user_b)

        user_c = User(123)
        self.assertIs(user_a, user_c)
    def test_fetch_resource_uri_id(self):
        client = Client('http://example.com', fetch_schema=False)

        User = client.resource_factory('user', {
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                }
            },
            "links": [
                {
                    "rel": "self",
                    "method": "GET",
                    "href": "/user/{id}",
                    "schema": {
                        "$ref": "#"
                    }
                }
            ]
        })

        # should not result in a fetch:
        self.assertEqual('/user/123', uri_for(User('/user/123')))
        self.assertEqual(123, User('/user/123').id)
    def test_instance_cache(self):
        responses.add(responses.GET, 'http://example.com/schema', json={
            "properties": {}
        })

        client = Client('http://example.com')

        foo_a = client.instance('/foo')
        foo_b = client.instance('/foo')
        self.assertIs(foo_a, foo_b)
    def test_response_errors(self):
        client = Client('http://example.com', fetch_schema=False)

        User = client.resource_factory('user', {
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                }
            },
            "links": [
                {
                    "rel": "self",
                    "method": "GET",
                    "href": "/user/{id}"
                },
                {
                    "rel": "makeAdmin",
                    "method": "POST",
                    "href": "/user/{id}/make-admin"
                }
            ]
        })

        responses.add(responses.GET, 'http://example.com/user/1', status=404, json={
            "status": 404,
            "message": "Not Found"
        })

        responses.add(responses.POST, 'http://example.com/user/2/make-admin', status=400, json={
            "status": 400,
            "message": "Forbidden"
        })


        with self.assertRaises(HTTPError) as ctx:
            User.fetch(1)

        self.assertEqual(404, ctx.exception.response.status_code)

        with self.assertRaises(HTTPError) as ctx:
            User.make_admin(id=2)

        self.assertEqual(400, ctx.exception.response.status_code)
    def test_first(self):
        client = Client('http://example.com', fetch_schema=False)

        User = client.resource_factory('user', {
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                }
            },
            "links": [
                {
                    "rel": "instances",
                    "method": "GET",
                    "href": "/user",
                    "schema": {
                        "type": "object",
                        "properties": {
                            "page": {"type": "number"},
                            "per_page": {"type": "number"},
                        }
                    }
                }
            ]
        })

        def request_callback(request):
            if 'missing' in request.url:
                return 200, {}, '[]'
            return 200, {}, json.dumps([{"$uri": "/user/1", "name": "foo"}])

        responses.add_callback(responses.GET, 'http://example.com/user',
                               callback=request_callback,
                               content_type='application/json')

        self.assertTrue(User.instances.returns_pagination())

        foo = User.first(where={"name": "foo"})
        self.assertEqual("foo", foo.name)

        with self.assertRaises(ItemNotFound):
            missing = User.first(where={"name": "missing"})
    def test_fetch_instance(self):
        responses.add(responses.GET, 'http://example.com/api/schema', json={
            "properties": {
                "user": {"$ref": "/api/user/schema#"}
            }
        })

        responses.add(responses.GET, 'http://example.com/api/user/schema', json={
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            },
            "links": [
                {
                    "rel": "self",
                    "href": "/api/user/{id}",
                    "method": "GET"
                }
            ]
        })

        responses.add(responses.GET, 'http://example.com/api/user/123', json={
            "$uri": "/api/user/123",
            "name": "foo"
        })

        client = Client('http://example.com/api')
        user = client.User.fetch(123)

        self.assertEqual({
            "$uri": "/api/user/123",
            "name": "foo"
        }, dict(user))

        self.assertEqual(client.instance('/api/user/123'), user)
        self.assertEqual("foo", user.name)
        self.assertEqual(123, user.id)

        self.assertEqual(user, client.User(123)._self())
    def test_create(self):
        responses.add(responses.GET, 'http://example.com/api/schema', json={
            "properties": {
                "user": {"$ref": "/api/user/schema#"}
            }
        })

        responses.add(responses.GET, 'http://example.com/api/user/schema', json={
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            },
            "links": [
                {
                    "rel": "self",
                    "href": "/api/user/{id}",
                    "method": "GET"
                },
                {
                    "rel": "create",
                    "href": "/api/user",
                    "method": "POST"
                }
            ]
        })

        def request_callback(request):
            request_data = json.loads(request.body)
            self.assertEqual({"name": "Mr. Foo"}, request_data)
            return 201, {}, json.dumps({
                '$uri': '/api/user/{}'.format(1),
                'name': request_data['name']
            })

        responses.add_callback(responses.POST, 'http://example.com/api/user',
                               callback=request_callback,
                               content_type='application/json')

        client = Client('http://example.com/api')

        user = client.User.create(name="Mr. Foo")

        self.assertEqual(1, user.id)
        self.assertEqual('Mr. Foo', user.name)
    def test_send_single_value(self):
        responses.add(responses.GET, 'http://example.com/api/schema', json={
            "properties": {
                "button": {"$ref": "/button/schema#"}
            }
        })

        responses.add(responses.GET, 'http://example.com/button/schema', json={
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            },
            "links": [
                {
                    "rel": "toggleAll",
                    "href": "/button/toggle-all",
                    "method": "POST",
                    "schema": {
                        "type": "boolean"
                    }
                }
            ]
        })

        def request_callback(request):
            request_data = json.loads(request.body)
            return 201, {}, json.dumps(request_data)

        responses.add_callback(responses.POST, 'http://example.com/button/toggle-all',
                               callback=request_callback,
                               content_type='application/json')

        client = Client('http://example.com/api')

        result = client.Button.toggle_all(False)
        self.assertEqual(False, result)

        result = client.Button.toggle_all(True)
        self.assertEqual(True, result)
    def test_read_schema(self):
        responses.add(responses.GET, 'http://example.com/api/schema', json={
            "properties": {
                "user": {"$ref": "/api/user/schema#"}
            }
        })

        responses.add(responses.GET, 'http://example.com/api/user/schema', json={
            "type": "object",
            "description": "The description for 'user'.",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "The description for 'user.name'."
                }
            },
            "links": [
                {
                    "rel": "self",
                    "href": "/api/user/{id}",
                    "method": "GET",
                    "targetSchema": {
                        "$ref": "#"
                    }
                }
            ]
        })

        client = Client('http://example.com/api')

        self.assertTrue(issubclass(client.User, Resource))
        self.assertEqual("The description for 'user'.", client.User.__doc__)
        self.assertIsInstance(client.User.name, property)
        self.assertEqual("The description for 'user.name'.", client.User.name.__doc__)

        self.assertEqual({
            'self': client.User._self.link
        }, client.User._links)
    def test_resource_relationships(self):

        responses.add(responses.GET, 'http://example.com/api/schema', json={
            "properties": {
                "user": {"$ref": "/api/user/schema#"},
                "vehicle": {"$ref": "/api/vehicle/schema#"}
            }
        })

        responses.add(responses.GET, 'http://example.com/api/user/schema', json={
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            },
            "links": [
                {
                    "rel": "self",
                    "href": "/api/user/{id}",
                    "method": "GET"
                }
            ]
        })

        responses.add(responses.GET, 'http://example.com/api/vehicle/schema', json={
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                },
                "owner": {
                    "additionalProperties": False,
                    "properties": {
                        "$ref": {
                            "pattern": "^\\/api\\/users\\/[^/]+$",
                            "type": "string"
                        }
                    },
                    "readOnly": True,
                    "type": "object"
                },
            },
            "links": [
                {
                    "rel": "self",
                    "method": "GET",
                    "href": "/api/vehicle/{id}",
                    "schema": {
                        "$ref": "#"
                    }
                }
            ]
        })

        responses.add(responses.GET, 'http://example.com/api/user/123', json={
            "$uri": "/api/user/123",
            "name": "foo"
        })

        responses.add(responses.GET, 'http://example.com/api/vehicle/456', json={
            "$uri": "/api/vehicle/456",
            "name": "Bus",
            "wheels": 6,
            "owner": {"$ref": "/api/user/123"}
        })

        client = Client('http://example.com/api')
        vehicle = client.Vehicle.fetch(456)
        self.assertEqual(200, vehicle._status)
        vehicle.name = "Minivan"
        owner = vehicle.owner  # Doesn't set _properties, thus status code is None
        self.assertEqual(None, owner._status)
        self.assertEqual(4, len(responses.calls))  # 3 schema, 1 for vehicle GET

        owner.name = "Minivan Owner"  # Sets properties, access should trigger get too
        self.assertEqual(200, owner._status)  # Needs to get set when _properties are created
        self.assertEqual(5, len(responses.calls))  # 3 schema, vehicle GET, owner GET
    def test_subclass_resource(self):
        client = Client('http://example.com', fetch_schema=False)

        class CustomResource(Resource):

            def is_car(self):
                return self.wheels == 4

        Vehicle = client.resource_factory('vehicle', {
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                },
                "wheels": {
                    "type": "number"
                }
            },
            "links": [
                {
                    "rel": "instances",
                    "method": "GET",
                    "href": "/vehicle",
                    "schema": {
                        "type": "object",
                        "properties": {
                            "page": {"type": "number"},
                            "per_page": {"type": "number"},
                        }
                    }
                },
                {
                    "rel": "self",
                    "method": "GET",
                    "href": "/vehicle/{id}",
                    "schema": {
                        "$ref": "#"
                    }
                }
            ]
        }, resource_cls=CustomResource)

        self.assertEqual(True, Vehicle(name="Quad", wheels=4).is_car())
        self.assertEqual(False, Vehicle(name="Bike", wheels=2).is_car())

        responses.add(responses.GET, 'http://example.com/vehicle/2', json={
            "$uri": "/vehicle/2",
            "name": "Bus",
            "wheels": 6
        })

        responses.add(responses.GET, 'http://example.com/vehicle', json=[{
            "$uri": "/vehicle/2",
            "name": "Bus",
            "wheels": 6
        }])

        self.assertEqual(6, Vehicle(2).wheels)
        self.assertEqual(False, Vehicle(2).is_car())
        self.assertEqual([Vehicle(2)], list(Vehicle.instances()))
    def test_pagination(self):
        client = Client('http://example.com', fetch_schema=False)

        User = client.resource_factory('user', {
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string"
                }
            },
            "links": [
                {
                    "rel": "self",
                    "href": "/user/{id}",
                    "method": "GET"
                },
                {
                    "rel": "instances",
                    "method": "GET",
                    "href": "/user",
                    "schema": {
                        "type": "object",
                        "properties": {
                            "page": {
                                "default": 1,
                                "minimum": 1,
                                "type": "integer"
                            },
                            "per_page": {
                                "default": 20,
                                "maximum": 100,
                                "minimum": 1,
                                "type": "integer"
                            }
                        }
                    },
                    "target_schema": {
                        "$ref": "#"
                    }
                }
            ]
        })

        def request_callback(request):
            users = [
                {
                    "$uri": "/user/{}".format(i),
                    "name": "user-{}".format(i)
                } for i in range(1, 36)
                ]

            params = parse_qs(urlparse(request.url).query)
            offset = (int(params['page'][0]) - 1) * int(params['per_page'][0])
            return 200, {'X-Total-Count': '35'}, json.dumps(users[offset:offset + int(params['per_page'][0])])

        responses.add_callback(responses.GET, 'http://example.com/user',
                               callback=request_callback,
                               content_type='application/json')

        result = User.instances()

        # TODO test: result = User.instances(where={"foo": {"$gt": 123}})
        # TODO magic: result = User.instances.where(foo__gt=123).sort(foo=DESC)

        self.assertIsInstance(result, PaginatedList)
        self.assertEqual(35, len(result))
        self.assertEqual(1, len(result._pages))
        self.assertEqual([
                             {
                                 "$uri": "/user/{}".format(i),
                                 "name": "user-{}".format(i)
                             } for i in range(1, 36)
                             ], list(result))
        self.assertEqual(2, len(result._pages))
        self.assertEqual(20, len(result._pages[1]))
        self.assertEqual(15, len(result._pages[2]))
    def test_encode_decode_date(self):
        client = Client('http://example.com', fetch_schema=False)

        Event = client.resource_factory('event', {
            "type": "object",
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "start_date": {
                    "type": "object",
                    "additionalProperties": False,
                    "properties": {
                        "$date": {
                            "type": "integer"
                        }
                    }
                }
            },
            "links": [
                {
                    "rel": "instances",
                    "method": "GET",
                    "href": "/event"
                },
                {
                    "rel": "create",
                    "method": "POST",
                    "href": "/event",
                    "schema": {
                        "type": "object",
                        "additionalProperties": False,
                        "properties": {
                            "start_date": {
                                "type": "object",
                                "additionalProperties": False,
                                "properties": {
                                    "$date": {
                                        "type": "integer"
                                    }
                                }
                            }
                        }
                    }
                }
            ]
        })

        responses.add(responses.GET, 'http://example.com/event', json=[
            {
                "$uri": "/event/1",
                "start_date": {
                    "$date": 1451060269000
                }
            }
        ])

        events = Event.instances()
        self.assertEqual(1, len(events))
        self.assertEqual({
            "$uri": "/event/1",
            "start_date": datetime(2015, 12, 25, 16, 17, 49, tzinfo=timezone.utc)
        }, events[0]._properties)


        def request_callback(request):
            request_data = json.loads(request.body)
            self.assertEqual({
                "start_date": {
                    "$date": 1451060269000
                }
            }, request_data)
            return 201, {}, json.dumps({
                "$uri": "/event/2",
                "start_date": request_data['start_date']
            })

        responses.add_callback(responses.POST, 'http://example.com/event',
                               callback=request_callback,
                               content_type='application/json')

        self.assertEqual(False, Event._create.schema.can_include_property('$uri'))
        self.assertEqual(True, Event._create.schema.can_include_property('start_date'))

        event = Event()
        event.start_date = datetime(2015, 12, 25, 16, 17, 49, tzinfo=timezone.utc)
        event.save()

        self.assertEqual(2, event.id)
        self.assertEqual({
            "$uri": "/event/2",
            "start_date": datetime(2015, 12, 25, 16, 17, 49, tzinfo=timezone.utc)
        }, event._properties)
    def test_api_uri_with_port(self):
        responses.add(responses.GET, 'http://example.com:5000/api/schema', json={
            "properties": {}
        })

        client = Client('http://example.com:5000/api')
    def test_read_only_properties(self):
        responses.add(responses.GET, 'http://example.com/schema', json={
            "properties": {
                "user": {"$ref": "/user/schema#"}
            }
        })

        responses.add(responses.GET, 'http://example.com/user/schema', json={
            "type": "object",
            "additionalProperties": False,
            "properties": {
                "$uri": {
                    "type": "string",
                    "readOnly": True
                },
                "name": {
                    "type": "string",
                    "readOnly": True
                },
                "age": {
                    "type": "integer"
                }
            },
            "links": [
                {
                    "rel": "self",
                    "href": "/user/{id}",
                    "method": "GET"
                },
                {
                    "rel": "update",
                    "href": "/user/{id}",
                    "method": "PATCH",
                    "schema": {
                        "$ref": "#"
                    }
                }
            ]
        })

        client = Client('http://example.com')

        def request_callback(request):
            request_data = json.loads(request.body)
            self.assertEqual(1, len(request_data))
            self.assertTrue("age" in request_data)
            self.assertTrue(request_data['age'] in (20, 21))
            return 201, {}, json.dumps({
                "$uri": "/user/1",
                "age": request_data['age']
            })

        responses.add_callback(responses.PATCH, 'http://example.com/user/1',
                               callback=request_callback,
                               content_type='application/json')

        user = client.User(1, name='Foo')
        with self.assertRaises(AttributeError):
            user.name = 'Bar'
        user.age = 20
        user.save()

        user.update(name='Bar', age=21)
        self.assertEqual(user.age, 21)