def test_multiput_existing_resource_through_main_data(self): res = self.client.put('/zoo_employee/', data=jsondumps({ 'data': [{ 'id': self.zoo_employee.id, 'name': 'add okay', }] }), content_type='application/json') self.assertEqual(res.status_code, 403) response_data = jsonloads(res.content) assert_json( response_data, { 'code': 'Forbidden', # We actually get a different error: "you do not have a scope that allows..." #'required_permission': 'testapp.change_zooemployee', EXTRA(): None, }) res = self.client.put('/zoo_employee/', data=jsondumps({ 'data': [{ 'id': self.zoo_employee.id, 'name': 'change okay', }] }), content_type='application/json') self.assertEqual(res.status_code, 200)
def test_post_new_resource(self): res = self.client.post('/zoo_employee/', data=jsondumps({ 'zoo': self.zoo.id, 'name': 'change okay', }), content_type='application/json') self.assertEqual(res.status_code, 403) response_data = jsonloads(res.content) assert_json( response_data, { 'code': 'Forbidden', # We actually get a different error: "you do not have a scope that allows..." #'required_permission': 'testapp.add_zooemployee', EXTRA(): None, }) res = self.client.post('/zoo_employee/', data=jsondumps({ 'zoo': self.zoo.id, 'name': 'add okay', }), content_type='application/json') self.assertEqual(res.status_code, 200)
def test_writes_to_write_once_fields_are_blocked_for_updates(self): res = self.client.post('/caretaker/', data=jsondumps({ 'name': 'Fabby', 'first_seen': '2020-01-01T00:00:00Z' }), content_type='application/json') self.assertEqual(res.status_code, 200) data = jsonloads(res.content) self.assertEqual([], data['_meta']['ignored_fields']) dt = parse_datetime('2020-01-01T00:00:00Z') self.assertEqual(dt, parse_datetime(data['first_seen'])) caretaker = Caretaker.objects.get(id=data['id']) self.assertEqual(dt, caretaker.first_seen) self.assertEqual('Fabby', caretaker.name) res = self.client.put('/caretaker/%s/' % caretaker.id, data=jsondumps({ 'name': 'Mr Fabby', 'first_seen': '2020-02-01T00:00:00Z' }), content_type='application/json') self.assertEqual(res.status_code, 200) data = jsonloads(res.content) self.assertEqual(['first_seen'], data['_meta']['ignored_fields']) self.assertEqual(dt, parse_datetime(data['first_seen'])) caretaker.refresh_from_db() self.assertEqual(dt, caretaker.first_seen) self.assertEqual('Mr Fabby', caretaker.name) # A put without the field means it's not in ignored_fields res = self.client.put('/caretaker/%s/' % caretaker.id, data=jsondumps({'name': 'Mrs Fabby'}), content_type='application/json') self.assertEqual(res.status_code, 200) data = jsonloads(res.content) self.assertEqual([], data['_meta']['ignored_fields']) self.assertEqual(dt, parse_datetime(data['first_seen'])) caretaker.refresh_from_db() self.assertEqual(dt, caretaker.first_seen) self.assertEqual('Mrs Fabby', caretaker.name)
def test_cannot_delete_on_multiput_without_delete_permission(self): country = Country.objects.create(name='Nederland') city1 = City.objects.create(country=country, name='Amsterdam') city2 = City.objects.create(country=country, name='Leeuwarden') # Now suppose Friesland is finally going to separate res = self.client.put('/country/', data=jsondumps({ 'data': [{ 'id': country.pk, 'name': 'Nederland', 'cities': [city1.pk] }] })) # This is not ok self.assertEquals(403, res.status_code) content = jsonloads(res.content) self.assertEquals('testapp.delete_city', content['required_permission']) # City 2 still exists! city2.refresh_from_db() self.assertEquals(country, city2.country) # And belongs to the nederlands
def test_transaction(self): requests = [ # This request should work fine { 'method': 'POST', 'path': '/animal/', 'body': { 'name': 'Foo', }, }, # This one should error { 'method': 'POST', 'path': '/animal/', 'body': { 'NONEXISTINGFIELD': 'Bar', }, }, ] response = self.client.post( '/multi/', data=jsondumps(requests), content_type='application/json', ) self.assertEqual(response.status_code, 418) response = jsonloads(response.content) with self.assertRaises(Animal.DoesNotExist): Animal.objects.get(pk=response[0]['body']['id'])
def test_multiput_deletions_no_perm(self): country = Country.objects.create(name='Netherlands') res = self.client.put('/country/'.format(country.pk), data=jsondumps({ 'deletions': [country.pk], })) self.assertEquals(403, res.status_code) country.refresh_from_db()
def test_multiput_deletions(self): country = Country.objects.create(name='Netherlands') res = self.client.put('/country/'.format(country.pk), data=jsondumps({ 'deletions': [country.pk], })) self.assertEquals(200, res.status_code) with self.assertRaises(Country.DoesNotExist): country.refresh_from_db()
def identify(self, request): """ Simple endpoint, to identify own user :param request: :return: """ me = self.get_queryset(request).get(pk=request.user.pk) return HttpResponse( jsondumps({ 'username': me.username, 'email': me.email }))
def test_related_object_nullable_on_delete_is_set_to_null(self): country = Country.objects.create(name='Belgium') city1 = CityState.objects.create(country=country, name='Brussels') res = self.client.put('/country/{}/'.format(country.pk), data=jsondumps({ 'id': country.pk, 'name': 'Belgium', 'city_states': [] })) self.assertEquals(200, res.status_code) city1.refresh_from_db() self.assertIsNone(city1.country)
def test_softdelete_on_put_with_delete_permission_softdeletable(self): country = Country.objects.create(name='Nederland') city1 = PermanentCity.objects.create(country=country, name='Rotterdam', deleted=False) res = self.client.put('/country/{}/'.format(country.pk), data=jsondumps({ 'id': country.pk, 'name': 'Nederland', 'permanent_cities': [] })) self.assertEquals(200, res.status_code) city1.refresh_from_db() self.assertTrue(city1.deleted)
def test_json_datetimes_dump_and_load_correctly(self): encoded = { # Include a few non-datetime objects to check # we defer to standard json encoding for them. 'arr': ['matey'], 'nest': { 'num': 1234, 'some_datetime': datetime.strptime('2016-01-01 12:00:00.123456+0000', '%Y-%m-%d %H:%M:%S.%f%z'), }, 'the_datetime': datetime.strptime('2016-06-21 01:02:03+0000', '%Y-%m-%d %H:%M:%S%z'), 'timezoned_datetime': datetime.strptime('2016-10-04 11:28:20+0200', '%Y-%m-%d %H:%M:%S%z'), 'plain_date': date(1998, 2, 3), } # We can't check directly against the serialized # string because dicts have no ordering, and space # usage might differ. So instead, we load with the # core JSON parser and compare the raw string values. plain = { 'arr': ['matey'], 'nest': { 'num': 1234, 'some_datetime': '2016-01-01T12:00:00.123456+0000', }, 'the_datetime': '2016-06-21T01:02:03.000000+0000', 'timezoned_datetime': '2016-10-04T11:28:20.000000+0200', 'plain_date': '1998-02-03', } # Dumping to json self.assertEqual( plain, python_core_json.loads(binder_json.jsondumps(encoded))) # Loading from json, currently just defers to plain # JSON (after decoding). self.assertEqual( plain, binder_json.jsonloads(bytes(python_core_json.dumps(plain), 'utf-8')))
def test_can_delete_on_put_with_delete_permission(self): country = Country.objects.create(name='Nederland') city1 = City.objects.create(country=country, name='Amsterdam') city2 = City.objects.create(country=country, name='Leeuwarden') res = self.client.put('/country/{}/'.format(country.pk), data=jsondumps({ 'id': country.pk, 'name': 'Nederland', 'cities': [city1.pk] })) self.assertEquals(200, res.status_code) with self.assertRaises(City.DoesNotExist): city2.refresh_from_db() self.assertEquals(1, country.cities.count())
def test_softdelete_on_put_without_softdelete_permission_fails(self): country = Country.objects.create(name='Nederland') city1 = PermanentCity.objects.create(country=country, name='Rotterdam', deleted=False) res = self.client.put('/country/{}/'.format(country.pk), data=jsondumps({ 'id': country.pk, 'name': 'Nederland', 'permanent_cities': [] })) self.assertEquals(res.status_code, 403) content = jsonloads(res.content) self.assertEquals('testapp.delete_permanentcity', content['required_permission'])
def test_create_and_update(self): requests = [ { 'method': 'POST', 'path': '/zoo/', 'body': { 'name': 'Zoo', }, }, { 'method': 'POST', 'path': '/animal/', 'body': { 'name': 'Foo', }, 'transforms': [{ 'source': [0, 'body', 'id'], 'target': ['body', 'zoo'], }], }, { 'method': 'PUT', 'path': '/animal/{id}/', 'body': { 'name': 'Bar', }, 'transforms': [{ 'source': [1, 'body', 'id'], 'target': ['path', 'id'], }], }, ] response = self.client.post( '/multi/', data=jsondumps(requests), content_type='application/json', ) self.assertEqual(response.status_code, 200)
def test_multiput_new_resource_through_with(self): res = self.client.put('/zoo/', data=jsondumps({ 'data': [], 'with': { 'zoo_employee': [{ 'id': -1, }] } }), content_type='application/json') self.assertEqual(res.status_code, 403) response_data = jsonloads(res.content) assert_json( response_data, { 'code': 'Forbidden', 'required_permission': 'testapp.add_zooemployee', EXTRA(): None, })
def test_related_object_nullable_on_delete_no_change_permission_not_allowed( self): country = Country.objects.create(name='Belgium') city1 = CityState.objects.create(country=country, name='Brussels') res = self.client.put('/country/{}/'.format(country.pk), data=jsondumps({ 'id': country.pk, 'name': 'Belgium', 'city_states': [] })) self.assertEquals(403, res.status_code) content = jsonloads(res.content) self.assertEquals('testapp.change_citystate', content['required_permission']) city1.refresh_from_db() self.assertEquals(country.pk, country.pk)
def test_delete_scoping_on_multiput_with_delete_permission(self): country = Country.objects.create(name='Nederland') city1 = City.objects.create(country=country, name='Amsterdam') city2 = City.objects.create(country=country, name='Leeuwarden') # Now suppose Friesland is finally going to separate res = self.client.put('/country/', data=jsondumps({ 'data': [{ 'id': country.pk, 'name': 'Nederland', 'cities': [city1.pk] }] })) self.assertEquals(200, res.status_code) # City 2 must not exist. with self.assertRaises(City.DoesNotExist): city2.refresh_from_db() self.assertEquals(1, country.cities.count())
def test_cannot_change_on_multiput_without_change_permission(self): country = Country.objects.create(name='Nederland') city1 = City.objects.create(country=country, name='Amsterdam') res = self.client.put('/country/', data=jsondumps({ 'data': [{ 'id': country.pk, 'name': 'Nederland', 'cities': [city1.pk] }], 'with': { 'city': [{ 'id': city1.pk, 'name': 'Rotterdam', }] } })) # This is not ok self.assertEquals(403, res.status_code) content = jsonloads(res.content) self.assertEquals('testapp.change_city', content['required_permission'])
def test_uuids_dump_correctly(self): u = UUID('{12345678-1234-5678-1234-567812345678}') self.assertEqual('["12345678-1234-5678-1234-567812345678"]', binder_json.jsondumps([u]))
def test_json_datetimes_dump_correctly_tz_us(self): t = datetime(2016, 1, 1, 1, 2, 3, 313337, tzinfo=timezone.utc) self.assertEqual('["2016-01-01T01:02:03.313337+0000"]', binder_json.jsondumps([t]))
def test_json_datetimes_dump_correctly_notz_us(self): t = datetime(2016, 1, 1, 1, 2, 3, 313337) self.assertEqual('["2016-01-01T01:02:03.313337"]', binder_json.jsondumps([t]))
def test_writes_to_unwritable_fields_are_blocked_for_new_objects_and_updates( self): res = self.client.post('/caretaker/', data=jsondumps({ 'name': 'Fabby', 'last_seen': '2020-01-01T00:00:00Z' }), content_type='application/json') self.assertEqual(res.status_code, 200) data = jsonloads(res.content) self.assertEqual(['last_seen'], data['_meta']['ignored_fields']) self.assertIsNone(data['last_seen']) caretaker = Caretaker.objects.get(id=data['id']) self.assertIsNone(caretaker.last_seen) self.assertEqual('Fabby', caretaker.name) res = self.client.put('/caretaker/%s/' % caretaker.id, data=jsondumps({ 'name': 'Mr Fabby', 'last_seen': '2020-01-01T00:00:00Z' }), content_type='application/json') self.assertEqual(res.status_code, 200) data = jsonloads(res.content) self.assertEqual(['last_seen'], data['_meta']['ignored_fields']) self.assertIsNone(data['last_seen']) caretaker.refresh_from_db() self.assertIsNone(caretaker.last_seen) self.assertEqual('Mr Fabby', caretaker.name) # Just a paranoid check that we can't overwrite it, even if it has a value dt = parse_datetime('2020-07-31T12:34:56Z') caretaker.last_seen = dt caretaker.save() res = self.client.put('/caretaker/%s/' % caretaker.id, data=jsondumps({ 'name': 'Mrs Fabby', 'last_seen': '2020-01-01T00:00:00Z' }), content_type='application/json') self.assertEqual(res.status_code, 200) data = jsonloads(res.content) self.assertEqual(['last_seen'], data['_meta']['ignored_fields']) self.assertEqual(dt, parse_datetime(data['last_seen'])) caretaker.refresh_from_db() self.assertEqual(dt, caretaker.last_seen) self.assertEqual('Mrs Fabby', caretaker.name) # A put without the field means it's not in ignored_fields res = self.client.put('/caretaker/%s/' % caretaker.id, data=jsondumps({'name': 'just Fabby'}), content_type='application/json') self.assertEqual(res.status_code, 200) data = jsonloads(res.content) self.assertEqual([], data['_meta']['ignored_fields']) self.assertEqual(dt, parse_datetime(data['last_seen'])) caretaker.refresh_from_db() self.assertEqual(dt, caretaker.last_seen) self.assertEqual('just Fabby', caretaker.name)
def foo(self, request, obj): return jsondumps({})
def foo(self, request, obj): that.assertTrue(isinstance(obj, Country)) that.assertEqual(country.pk, obj.pk) return jsondumps({})
def parse_request(data, allowed_methods, responses, request): if not isinstance(data, dict): raise BinderRequestError('requests should be dicts') # Transform data transforms = data.pop('transforms', []) str_params = defaultdict(dict) if not isinstance(transforms, list): raise BinderRequestError('transforms should be a list') for transform in transforms: if 'source' not in transform: raise BinderRequestError('transforms require the field source') if 'target' not in transform: raise BinderRequestError('transforms require the field target') if (not isinstance(transform['source'], list) or len(transform['source']) < 1): raise BinderRequestError('source must be a non empty list') if (not isinstance(transform['target'], list) or len(transform['target']) < 2): raise BinderRequestError('target must be a non empty list') # Get value through source value = responses for key in transform['source']: if not isinstance(value, (list, dict)): raise BinderRequestError( 'source can only iterate through lists and dicts') try: value = value[key] except (KeyError, IndexError): raise BinderRequestError( 'invalid source {}, error at key {}'.format( transform['source'], key)) # Set value according to target target = data target_key = transform['target'][0] for i, key in enumerate(transform['target'][1:]): if isinstance(target, (list, dict)): try: target = target[target_key] except (KeyError, IndexError): raise BinderRequestError( 'invalid target {}, error at key {}'.format( transform['target'], target_key)) target_key = key else: raise BinderRequestError( 'target can only iterate through lists and dicts') if isinstance(target, (list, dict)): try: target[target_key] = value except IndexError: raise BinderRequestError( 'invalid target {}, error at key {}'.format( transform['target'], target_key)) elif isinstance(target, str): str_params[tuple( transform['target'][:-1])][transform['target'][-1]] = value else: raise BinderRequestError( 'target can only modify lists, dicts and strs') try: for keys, params in str_params.items(): target = data target_key = keys[0] for key in keys[1:]: target = target[target_key] target_key = key s = target[target_key] try: s = s.format(**params) except KeyError as e: raise BinderRequestError( 'str formatting at {}, missing key: {}'.format( keys, e.args[0])) target[target_key] = target[target_key].format(**params) except Exception: # All kind of things can go wrong when the data is altered through # a transform that occured after the str_params where determined # causing the target not to exist anymore or not be a str raise BinderRequestError( 'transforms altered data in such a way that str params became ' 'invalid') # Validate request if 'method' not in data: raise BinderRequestError('requests require the field method') if 'path' not in data: raise BinderRequestError('requests require the field path') # Validate method is allowed if data['method'] not in allowed_methods: raise BinderMethodNotAllowed() # Create request req = HttpRequest() req.method = data['method'] req.path = data['path'] req.path_info = req.path req.COOKIES = request.COOKIES req.META = request.META req.content_type = 'application/json' if 'body' in data: req._body = jsondumps(data['body']).encode() else: req._body = b'' return req
def test_decimals_dump_correctly(self): u = Decimal('1.1') self.assertEqual('["1.1"]', binder_json.jsondumps([u]))
def test_json_datetimerange_dump_correctly(self): t = datetime(2016, 1, 1, 1, 2, 3, 313337, tzinfo=timezone.utc) d = DateTimeTZRange(t, t) self.assertEqual( '["2016-01-01T01:02:03.313337+0000", "2016-01-01T01:02:03.313337+0000"]', binder_json.jsondumps(d))
def test_nontimezoned_json_datetimes_dump_correctly(self): t = datetime.strptime('2016-01-01 01:02:03', '%Y-%m-%d %H:%M:%S') self.assertEqual('["2016-01-01T01:02:03.000000+0000"]', binder_json.jsondumps([t]))