class KintoRecords(Records): def _load(self): self.client = Client(server_url=self.options['server'], auth=self.options['auth'], bucket=self.options['bucket_name'], collection=self.options['collection_name']) # Create bucket self.client.create_bucket() self.client.create_collection(self.options['collection_name'], permissions=self.options['permissions']) # XXX to be removed later # remove the 'onecrl' bucket if it exists try: self.client.delete_bucket('onecrl') except KintoException: pass return [self._kinto2rec(rec) for rec in self.client.get_records()] def _kinto2rec(self, record): return record def delete(self, data): self.client.delete_record(data['id']) def create(self, data): if 'id' not in data: data['id'] = create_id(data) rec = self.client.create_record(data) return rec
class KintoRecords(Records): def _load(self): self.client = Client(server_url=self.options['server'], auth=self.options['auth'], bucket=self.options['bucket_name'], collection=self.options['collection_name']) # Create bucket try: self.client.create_bucket() except KintoException as e: if e.response.status_code != 412: raise e try: self.client.create_collection( permissions=self.options['permissions']) except KintoException as e: if e.response.status_code != 412: raise e return [self._kinto2rec(rec) for rec in self.client.get_records()] def _kinto2rec(self, record): return record def delete(self, data): self.client.delete_record(data['id']) def create(self, data): if 'id' not in data: data['id'] = create_id(data) rec = self.client.create_record(data) return rec
def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() record = client.create_record({"foo": "bar"}) deleted = client.delete_record(record["data"]["id"]) assert deleted["deleted"] is True assert len(client.get_records()) == 0
def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record({'foo': 'bar'}) deleted = client.delete_record(record['data']['id']) assert deleted['deleted'] is True assert len(client.get_records()) == 0
class RecordTest(unittest.TestCase): def setUp(self): self.session = mock.MagicMock() self.client = Client( session=self.session, bucket='mybucket', collection='mycollection') def test_record_id_is_given_after_creation(self): mock_response(self.session, data={'id': 5678}) record = self.client.create_record({'foo': 'bar'}) assert 'id' in record['data'].keys() def test_generated_record_id_is_an_uuid(self): mock_response(self.session) self.client.create_record({'foo': 'bar'}) id = self.session.request.mock_calls[0][1][1].split('/')[-1] uuid_regexp = r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}' self.assertRegexpMatches(id, uuid_regexp) def test_records_handles_permissions(self): mock_response(self.session) self.client.create_record( {'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={'foo': 'bar', 'id': '1234'}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_collection_argument_takes_precedence(self): mock_response(self.session) # Specify a different collection name for the client and the operation. client = Client(session=self.session, bucket='mybucket', collection='wrong_collection') client.update_record(data={'id': '1234'}, collection='good_collection', permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/good_collection/records/1234', data={'id': '1234'}, headers=None, permissions=mock.sentinel.permissions) def test_record_id_is_derived_from_data_if_present(self): mock_response(self.session) self.client.create_record(data={'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_data_and_permissions_are_added_on_create(self): mock_response(self.session) data = {'foo': 'bar'} permissions = {'read': ['mle']} self.client.create_record( id='1234', data=data, permissions=permissions) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=permissions, headers=DO_NOT_OVERWRITE) def test_creation_sends_if_none_match_by_default(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record( id='1234', data=data) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=None, headers=DO_NOT_OVERWRITE) def test_creation_doesnt_add_if_none_match_when_overwrite(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record(id='1234', data=data, safe=False) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=None, headers=None) def test_records_issues_a_request_on_delete(self): mock_response(self.session) self.client.delete_record('1234') url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_issues_a_request_on_retrieval(self): mock_response(self.session, data={'foo': 'bar'}) record = self.client.get_record('1234') self.assertEquals(record['data'], {'foo': 'bar'}) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('get', url) def test_collection_can_retrieve_all_records(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}]) records = self.client.get_records() assert list(records) == [{'id': 'foo'}, {'id': 'bar'}] def test_pagination_is_followed(self): # Mock the calls to request. link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], ), ] records = self.client.get_records('bucket', 'collection') assert list(records) == [ {'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, {'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ] def test_pagination_supports_if_none_match(self): link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], ), ] self.client.get_records('bucket', 'collection', if_none_match="1234") # Check that the If-None-Match header is present in the requests. self.session.request.assert_any_call( 'get', '/buckets/collection/collections/bucket/records', headers={'If-None-Match': '"1234"'}, params={}) self.session.request.assert_any_call( 'get', link, headers={'If-None-Match': '"1234"'}, params={}) def test_collection_can_delete_a_record(self): mock_response(self.session, data={'id': 1234}) resp = self.client.delete_record(id=1234) assert resp == {'id': 1234} url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_delete_if_match(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record( collection='mycollection', bucket='mybucket', id='1', last_modified=1234) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with( 'delete', url, headers={'If-Match': '"1234"'}) def test_record_delete_if_match_not_included_if_not_safe(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record( collection='mycollection', bucket='mybucket', id='1', last_modified=1234, safe=False) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with( 'delete', url, headers=None) def test_update_record_gets_the_id_from_data_if_exists(self): mock_response(self.session) self.client.update_record( bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=None, permissions=None) def test_update_record_handles_last_modified(self): mock_response(self.session) self.client.update_record( bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}, last_modified=1234) headers = {'If-Match': '"1234"'} self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=headers, permissions=None) def test_patch_record_uses_the_patch_method(self): mock_response(self.session) self.client.patch_record( bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}) self.session.request.assert_called_with( 'patch', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=None, permissions=None) def test_update_record_raises_if_no_id_is_given(self): with self.assertRaises(KeyError) as cm: self.client.update_record( data={'foo': 'bar'}, # Omit the id on purpose here. bucket='mybucket', collection='mycollection' ) assert text_type(cm.exception) == ( "'Unable to update a record, need an id.'") def test_get_or_create_doesnt_raise_in_case_of_conflict(self): data = { 'permissions': mock.sentinel.permissions, 'data': {'foo': 'bar'} } self.session.request.side_effect = [ get_http_error(status=412), (data, None) ] returned_data = self.client.create_record( bucket="buck", collection="coll", data={'id': 1234, 'foo': 'bar'}, if_not_exists=True) # Should not raise. assert returned_data == data def test_get_or_create_raise_in_other_cases(self): self.session.request.side_effect = get_http_error(status=500) with self.assertRaises(KintoException): self.client.create_record( bucket="buck", collection="coll", data={'foo': 'bar'}, if_not_exists=True)
class RecordTest(unittest.TestCase): def setUp(self): self.session = mock.MagicMock() self.client = Client(session=self.session, bucket="mybucket", collection="mycollection") def test_record_id_is_given_after_creation(self): mock_response(self.session, data={"id": 5678}) record = self.client.create_record({"foo": "bar"}) assert "id" in record["data"].keys() def test_generated_record_id_is_an_uuid(self): mock_response(self.session) self.client.create_record({"foo": "bar"}) id = self.session.request.mock_calls[0][1][1].split("/")[-1] uuid_regexp = r"[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}" self.assertRegexpMatches(id, uuid_regexp) def test_records_handles_permissions(self): mock_response(self.session) self.client.create_record({"id": "1234", "foo": "bar"}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( "put", "/buckets/mybucket/collections/mycollection/records/1234", data={"foo": "bar", "id": "1234"}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE, ) def test_collection_argument_takes_precedence(self): mock_response(self.session) # Specify a different collection name for the client and the operation. client = Client(session=self.session, bucket="mybucket", collection="wrong_collection") client.update_record(data={"id": "1234"}, collection="good_collection", permissions=mock.sentinel.permissions) self.session.request.assert_called_with( "put", "/buckets/mybucket/collections/good_collection/records/1234", data={"id": "1234"}, headers=None, permissions=mock.sentinel.permissions, ) def test_record_id_is_derived_from_data_if_present(self): mock_response(self.session) self.client.create_record(data={"id": "1234", "foo": "bar"}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( "put", "/buckets/mybucket/collections/mycollection/records/1234", data={"id": "1234", "foo": "bar"}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE, ) def test_data_and_permissions_are_added_on_create(self): mock_response(self.session) data = {"foo": "bar"} permissions = {"read": ["mle"]} self.client.create_record(id="1234", data=data, permissions=permissions) url = "/buckets/mybucket/collections/mycollection/records/1234" self.session.request.assert_called_with( "put", url, data=data, permissions=permissions, headers=DO_NOT_OVERWRITE ) def test_creation_sends_if_none_match_by_default(self): mock_response(self.session) data = {"foo": "bar"} self.client.create_record(id="1234", data=data) url = "/buckets/mybucket/collections/mycollection/records/1234" self.session.request.assert_called_with("put", url, data=data, permissions=None, headers=DO_NOT_OVERWRITE) def test_creation_doesnt_add_if_none_match_when_overwrite(self): mock_response(self.session) data = {"foo": "bar"} self.client.create_record(id="1234", data=data, safe=False) url = "/buckets/mybucket/collections/mycollection/records/1234" self.session.request.assert_called_with("put", url, data=data, permissions=None, headers=None) def test_records_issues_a_request_on_delete(self): mock_response(self.session) self.client.delete_record("1234") url = "/buckets/mybucket/collections/mycollection/records/1234" self.session.request.assert_called_with("delete", url, headers=None) def test_record_issues_a_request_on_retrieval(self): mock_response(self.session, data={"foo": "bar"}) record = self.client.get_record("1234") self.assertEquals(record["data"], {"foo": "bar"}) url = "/buckets/mybucket/collections/mycollection/records/1234" self.session.request.assert_called_with("get", url) def test_collection_can_retrieve_all_records(self): mock_response(self.session, data=[{"id": "foo"}, {"id": "bar"}]) records = self.client.get_records() assert list(records) == [{"id": "foo"}, {"id": "bar"}] def test_pagination_is_followed(self): # Mock the calls to request. link = "http://example.org/buckets/buck/collections/coll/records/" "?token=1234" self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response([{"id": "1", "value": "item1"}, {"id": "2", "value": "item2"}], {"Next-Page": link}), # Second one returns a list of items without a pagination token. build_response([{"id": "3", "value": "item3"}, {"id": "4", "value": "item4"}]), ] records = self.client.get_records("bucket", "collection") assert list(records) == [ {"id": "1", "value": "item1"}, {"id": "2", "value": "item2"}, {"id": "3", "value": "item3"}, {"id": "4", "value": "item4"}, ] def test_pagination_supports_if_none_match(self): link = "http://example.org/buckets/buck/collections/coll/records/" "?token=1234" self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response([{"id": "1", "value": "item1"}, {"id": "2", "value": "item2"}], {"Next-Page": link}), # Second one returns a list of items without a pagination token. build_response([{"id": "3", "value": "item3"}, {"id": "4", "value": "item4"}]), ] self.client.get_records("bucket", "collection", if_none_match="1234") # Check that the If-None-Match header is present in the requests. self.session.request.assert_any_call( "get", "/buckets/collection/collections/bucket/records", headers={"If-None-Match": '"1234"'}, params={} ) self.session.request.assert_any_call("get", link, headers={"If-None-Match": '"1234"'}, params={}) def test_collection_can_delete_a_record(self): mock_response(self.session, data={"id": 1234}) resp = self.client.delete_record(id=1234) assert resp == {"id": 1234} url = "/buckets/mybucket/collections/mycollection/records/1234" self.session.request.assert_called_with("delete", url, headers=None) def test_collection_can_delete_a_list_of_records(self): self.client.delete_records(["1234", "5678"]) # url = '/buckets/mybucket/collections/mycollection/records/9' # XXX check that the delete is done in a BATCH. def test_record_delete_if_match(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record(collection="mycollection", bucket="mybucket", id="1", last_modified=1234) assert deleted == data url = "/buckets/mybucket/collections/mycollection/records/1" self.session.request.assert_called_with("delete", url, headers={"If-Match": '"1234"'}) def test_record_delete_if_match_not_included_if_not_safe(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record( collection="mycollection", bucket="mybucket", id="1", last_modified=1234, safe=False ) assert deleted == data url = "/buckets/mybucket/collections/mycollection/records/1" self.session.request.assert_called_with("delete", url, headers=None) def test_update_record_gets_the_id_from_data_if_exists(self): mock_response(self.session) self.client.update_record(bucket="mybucket", collection="mycollection", data={"id": 1, "foo": "bar"}) self.session.request.assert_called_with( "put", "/buckets/mybucket/collections/mycollection/records/1", data={"id": 1, "foo": "bar"}, headers=None, permissions=None, ) def test_update_record_handles_last_modified(self): mock_response(self.session) self.client.update_record( bucket="mybucket", collection="mycollection", data={"id": 1, "foo": "bar"}, last_modified=1234 ) headers = {"If-Match": '"1234"'} self.session.request.assert_called_with( "put", "/buckets/mybucket/collections/mycollection/records/1", data={"id": 1, "foo": "bar"}, headers=headers, permissions=None, ) def test_patch_record_uses_the_patch_method(self): mock_response(self.session) self.client.patch_record(bucket="mybucket", collection="mycollection", data={"id": 1, "foo": "bar"}) self.session.request.assert_called_with( "patch", "/buckets/mybucket/collections/mycollection/records/1", data={"id": 1, "foo": "bar"}, headers=None, permissions=None, ) def test_update_record_raises_if_no_id_is_given(self): with self.assertRaises(KeyError) as cm: self.client.update_record( data={"foo": "bar"}, bucket="mybucket", collection="mycollection" # Omit the id on purpose here. ) assert text_type(cm.exception) == ("'Unable to update a record, need an id.'") def test_get_or_create_doesnt_raise_in_case_of_conflict(self): data = {"permissions": mock.sentinel.permissions, "data": {"foo": "bar"}} self.session.request.side_effect = [get_http_error(status=412), (data, None)] returned_data = self.client.create_record( bucket="buck", collection="coll", data={"id": 1234, "foo": "bar"}, if_not_exists=True ) # Should not raise. assert returned_data == data def test_get_or_create_raise_in_other_cases(self): self.session.request.side_effect = get_http_error(status=500) with self.assertRaises(KintoException): self.client.create_record(bucket="buck", collection="coll", data={"foo": "bar"}, if_not_exists=True)
class FunctionalTest(unittest2.TestCase): def __init__(self, *args, **kwargs): super(FunctionalTest, self).__init__(*args, **kwargs) self.auth = DEFAULT_AUTH self.private_key = os.path.join(__HERE__, 'config/ecdsa.private.pem') self.signer_config = configparser.RawConfigParser() self.signer_config.read(os.path.join(__HERE__, 'config/signer.ini')) priv_key = self.signer_config.get( 'app:main', 'kinto.signer.ecdsa.private_key') self.signer = ECDSASigner(private_key=priv_key) # Setup the kinto clients for the source and destination. self._auth = DEFAULT_AUTH self._server_url = SERVER_URL self._source_bucket = "source" self._destination_bucket = "destination" self._collection_id = "collection1" self.source = Client( server_url=self._server_url, auth=self._auth, bucket=self._source_bucket, collection=self._collection_id) self.destination = Client( server_url=self._server_url, auth=self._auth, bucket=self._destination_bucket, collection=self._collection_id) def tearDown(self): # Delete all the created objects. self._flush_server(self._server_url) def _flush_server(self, server_url): flush_url = urljoin(server_url, '/__flush__') resp = requests.post(flush_url) resp.raise_for_status() def test_destination_creation_and_new_records_signature(self): self.source.create_bucket() self.source.create_collection() # Send new data to the signer. with self.source.batch() as batch: for n in range(0, 10): batch.create_record(data={'newdata': n}) source_records = self.source.get_records() assert len(source_records) == 10 # Trigger a signature. self.source.update_collection( data={'status': 'to-sign'}, method="put") # Ensure the remote data is signed properly. data = self.destination.get_collection() signature = data['data']['signature'] assert signature is not None records = self.destination.get_records() assert len(records) == 10 serialized_records = canonical_json(records) self.signer.verify(serialized_records, signature) # the status of the source collection should be "signed". source_collection = self.source.get_collection()['data'] assert source_collection['status'] == 'signed' def test_records_deletion_and_signature(self): self.source.create_bucket() self.source.create_collection() # Create some data on the source collection and send it. with self.source.batch() as batch: for n in range(0, 10): batch.create_record(data={'newdata': n}) source_records = self.source.get_records() assert len(source_records) == 10 # Trigger a signature. self.source.update_collection(data={'status': 'to-sign'}, method="put") # Wait so the new last_modified timestamp will be greater than the # one from the previous records. time.sleep(0.01) # Now delete one record on the source and trigger another signature. self.source.delete_record(source_records[0]['id']) self.source.update_collection(data={'status': 'to-sign'}, method="put") records = self.destination.get_records() assert len(records) == 9 data = self.destination.get_collection() signature = data['data']['signature'] assert signature is not None serialized_records = canonical_json(records) # This raises when the signature is invalid. self.signer.verify(serialized_records, signature)
class RecordTest(unittest.TestCase): def setUp(self): self.session = mock.MagicMock() self.client = Client(session=self.session, bucket='mybucket', collection='mycollection') def test_record_id_is_given_after_creation(self): mock_response(self.session, data={'id': 5678}) record = self.client.create_record({'foo': 'bar'}) assert 'id' in record['data'].keys() def test_generated_record_id_is_an_uuid(self): mock_response(self.session) self.client.create_record({'foo': 'bar'}) id = self.session.request.mock_calls[0][1][1].split('/')[-1] uuid_regexp = r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}' self.assertRegexpMatches(id, uuid_regexp) def test_records_handles_permissions(self): mock_response(self.session) self.client.create_record({ 'id': '1234', 'foo': 'bar' }, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={ 'foo': 'bar', 'id': '1234' }, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_collection_argument_takes_precedence(self): mock_response(self.session) # Specify a different collection name for the client and the operation. client = Client(session=self.session, bucket='mybucket', collection='wrong_collection') client.update_record(data={'id': '1234'}, collection='good_collection', permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/good_collection/records/1234', data={'id': '1234'}, headers=None, permissions=mock.sentinel.permissions) def test_record_id_is_derived_from_data_if_present(self): mock_response(self.session) self.client.create_record(data={ 'id': '1234', 'foo': 'bar' }, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={ 'id': '1234', 'foo': 'bar' }, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_data_and_permissions_are_added_on_create(self): mock_response(self.session) data = {'foo': 'bar'} permissions = {'read': ['mle']} self.client.create_record(id='1234', data=data, permissions=permissions) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('put', url, data=data, permissions=permissions, headers=DO_NOT_OVERWRITE) def test_creation_sends_if_none_match_by_default(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record(id='1234', data=data) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('put', url, data=data, permissions=None, headers=DO_NOT_OVERWRITE) def test_creation_doesnt_add_if_none_match_when_overwrite(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record(id='1234', data=data, safe=False) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('put', url, data=data, permissions=None, headers=None) def test_records_issues_a_request_on_delete(self): mock_response(self.session) self.client.delete_record('1234') url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_issues_a_request_on_retrieval(self): mock_response(self.session, data={'foo': 'bar'}) record = self.client.get_record('1234') self.assertEquals(record['data'], {'foo': 'bar'}) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('get', url) def test_collection_can_retrieve_all_records(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}]) records = self.client.get_records() assert list(records) == [{'id': 'foo'}, {'id': 'bar'}] def test_pagination_is_followed(self): # Mock the calls to request. link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response([ { 'id': '1', 'value': 'item1' }, { 'id': '2', 'value': 'item2' }, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response([ { 'id': '3', 'value': 'item3' }, { 'id': '4', 'value': 'item4' }, ], ), ] records = self.client.get_records('bucket', 'collection') assert list(records) == [ { 'id': '1', 'value': 'item1' }, { 'id': '2', 'value': 'item2' }, { 'id': '3', 'value': 'item3' }, { 'id': '4', 'value': 'item4' }, ] def test_pagination_supports_if_none_match(self): link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response([ { 'id': '1', 'value': 'item1' }, { 'id': '2', 'value': 'item2' }, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response([ { 'id': '3', 'value': 'item3' }, { 'id': '4', 'value': 'item4' }, ], ), ] self.client.get_records('bucket', 'collection', if_none_match="1234") # Check that the If-None-Match header is present in the requests. self.session.request.assert_any_call( 'get', '/buckets/collection/collections/bucket/records', headers={'If-None-Match': '"1234"'}, params={}) self.session.request.assert_any_call( 'get', link, headers={'If-None-Match': '"1234"'}, params={}) def test_collection_can_delete_a_record(self): mock_response(self.session, data={'id': 1234}) resp = self.client.delete_record(id=1234) assert resp == {'id': 1234} url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_delete_if_match(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record(collection='mycollection', bucket='mybucket', id='1', last_modified=1234) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with('delete', url, headers={'If-Match': '"1234"'}) def test_record_delete_if_match_not_included_if_not_safe(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record(collection='mycollection', bucket='mybucket', id='1', last_modified=1234, safe=False) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with('delete', url, headers=None) def test_update_record_gets_the_id_from_data_if_exists(self): mock_response(self.session) self.client.update_record(bucket='mybucket', collection='mycollection', data={ 'id': 1, 'foo': 'bar' }) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={ 'id': 1, 'foo': 'bar' }, headers=None, permissions=None) def test_update_record_handles_last_modified(self): mock_response(self.session) self.client.update_record(bucket='mybucket', collection='mycollection', data={ 'id': 1, 'foo': 'bar' }, last_modified=1234) headers = {'If-Match': '"1234"'} self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={ 'id': 1, 'foo': 'bar' }, headers=headers, permissions=None) def test_patch_record_uses_the_patch_method(self): mock_response(self.session) self.client.patch_record(bucket='mybucket', collection='mycollection', data={ 'id': 1, 'foo': 'bar' }) self.session.request.assert_called_with( 'patch', '/buckets/mybucket/collections/mycollection/records/1', data={ 'id': 1, 'foo': 'bar' }, headers=None, permissions=None) def test_update_record_raises_if_no_id_is_given(self): with self.assertRaises(KeyError) as cm: self.client.update_record( data={'foo': 'bar'}, # Omit the id on purpose here. bucket='mybucket', collection='mycollection') assert text_type( cm.exception) == ("'Unable to update a record, need an id.'") def test_get_or_create_doesnt_raise_in_case_of_conflict(self): data = { 'permissions': mock.sentinel.permissions, 'data': { 'foo': 'bar' } } self.session.request.side_effect = [ get_http_error(status=412), (data, None) ] returned_data = self.client.create_record( bucket="buck", collection="coll", data={ 'id': 1234, 'foo': 'bar' }, if_not_exists=True) # Should not raise. assert returned_data == data def test_get_or_create_raise_in_other_cases(self): self.session.request.side_effect = get_http_error(status=500) with self.assertRaises(KintoException): self.client.create_record(bucket="buck", collection="coll", data={'foo': 'bar'}, if_not_exists=True)