Example #1
0
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
Example #2
0
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
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
Example #4
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
Example #5
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
Example #6
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
Example #7
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)
Example #8
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_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)
Example #9
0
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)
Example #10
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)