diff --git a/CHANGES.md b/CHANGES.md index e1ddefd2..47e621f0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +# Unreleased + +- [FIXED] Unexpected keyword argument errors when using the library with the + `simplejson` module present in the environment caused by `requests` preferentially + loading it over the system `json` module. + # 2.10.0 (2018-09-19) - [NEW] Add custom JSON encoder/decoder option to `Document` constructor. diff --git a/Jenkinsfile b/Jenkinsfile index a806771f..1ab03a60 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,12 +10,13 @@ def getEnvForSuite(suiteName) { case 'basic': envVars.add("RUN_BASIC_AUTH_TESTS=1") break - case 'cookie': - break case 'iam': // Setting IAM_API_KEY forces tests to run using an IAM enabled client. envVars.add("IAM_API_KEY=$DB_IAM_API_KEY") break + case 'cookie': + case 'simplejson': + break default: error("Unknown test suite environment ${suiteName}") } @@ -36,6 +37,7 @@ def setupPythonAndTest(pythonVersion, testSuite) { . ./tmp/bin/activate pip install -r requirements.txt pip install -r test-requirements.txt + ${'simplejson'.equals(testSuite) ? 'pip install simplejson' : ''} pylint ./src/cloudant nosetests -A 'not db or (db is "cloudant" or "cloudant" in db)' -w ./tests/unit --with-xunit """ @@ -58,12 +60,15 @@ stage('Checkout'){ } stage('Test'){ - axes = [:] - ['2.7.12','3.5.2'].each { version -> + def py2 = '2.7.12' + def py3 = '3.5.2' + def axes = [:] + [py2, py3].each { version -> ['basic','cookie','iam'].each { auth -> axes.put("Python${version}-${auth}", {setupPythonAndTest(version, auth)}) } } + axes.put("Python${py3}-simplejson", {setupPythonAndTest(py3, 'simplejson')}) parallel(axes) } diff --git a/src/cloudant/_client_session.py b/src/cloudant/_client_session.py index 0e8fd386..820bc532 100644 --- a/src/cloudant/_client_session.py +++ b/src/cloudant/_client_session.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2015, 2018 IBM Corp. All rights reserved. +# Copyright (C) 2015, 2018 IBM Corp. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ from requests import RequestException, Session from ._2to3 import bytes_, unicode_, url_join +from ._common_util import response_to_json_dict from .error import CloudantException @@ -75,7 +76,7 @@ def info(self): resp = self.get(self._session_url) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def set_credentials(self, username, password): """ @@ -170,7 +171,7 @@ def request(self, method, url, **kwargs): is_expired = any(( resp.status_code == 403 and - resp.json().get('error') == 'credentials_expired', + response_to_json_dict(resp).get('error') == 'credentials_expired', resp.status_code == 401 )) @@ -282,10 +283,10 @@ def _get_access_token(self): 'apikey': self._api_key } ) - err = resp.json().get('errorMessage', err) + err = response_to_json_dict(resp).get('errorMessage', err) resp.raise_for_status() - return resp.json()['access_token'] + return response_to_json_dict(resp)['access_token'] except KeyError: raise CloudantException('Invalid response from IAM token service') diff --git a/src/cloudant/_common_util.py b/src/cloudant/_common_util.py index 1904a0a4..7a373815 100644 --- a/src/cloudant/_common_util.py +++ b/src/cloudant/_common_util.py @@ -270,7 +270,7 @@ def append_response_error_content(response, **kwargs): """ if response.status_code >= 400: try: - resp_dict = response.json() + resp_dict = response_to_json_dict(response) error = resp_dict.get('error', '') reason = resp_dict.get('reason', '') # Append to the existing response's reason @@ -279,6 +279,17 @@ def append_response_error_content(response, **kwargs): pass return response +def response_to_json_dict(response, **kwargs): + """ + Standard place to convert responses to JSON. + + :param response: requests response object + :param **kwargs: arguments accepted by json.loads + + :returns: dict of JSON response + """ + return json.loads(response.text, **kwargs) + # Classes diff --git a/src/cloudant/client.py b/src/cloudant/client.py index 06a6092c..9b22d0d7 100755 --- a/src/cloudant/client.py +++ b/src/cloudant/client.py @@ -35,6 +35,7 @@ USER_AGENT, append_response_error_content, CloudFoundryService, + response_to_json_dict, ) @@ -256,7 +257,7 @@ def all_dbs(self): url = '/'.join((self.server_url, '_all_dbs')) resp = self.r_session.get(url) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def create_database(self, dbname, **kwargs): """ @@ -345,7 +346,7 @@ def metadata(self): """ resp = self.r_session.get(self.server_url) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def keys(self, remote=False): """ @@ -622,7 +623,7 @@ def _usage_endpoint(self, endpoint, year=None, month=None): raise CloudantArgumentError(101, year, month) else: resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def bill(self, year=None, month=None): """ @@ -687,7 +688,7 @@ def shared_databases(self): self.server_url, '_api', 'v2', 'user', 'shared_databases')) resp = self.r_session.get(endpoint) resp.raise_for_status() - data = resp.json() + data = response_to_json_dict(resp) return data.get('shared_databases', []) def generate_api_key(self): @@ -699,7 +700,7 @@ def generate_api_key(self): endpoint = '/'.join((self.server_url, '_api', 'v2', 'api_keys')) resp = self.r_session.post(endpoint) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def cors_configuration(self): """ @@ -712,7 +713,7 @@ def cors_configuration(self): resp = self.r_session.get(endpoint) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def disable_cors(self): """ @@ -807,7 +808,7 @@ def _write_cors_configuration(self, config): ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) @classmethod def bluemix(cls, vcap_services, instance_name=None, service_name=None, **kwargs): diff --git a/src/cloudant/database.py b/src/cloudant/database.py index ec6c8b7a..51ff2430 100644 --- a/src/cloudant/database.py +++ b/src/cloudant/database.py @@ -26,7 +26,9 @@ SEARCH_INDEX_ARGS, SPECIAL_INDEX_TYPE, TEXT_INDEX_TYPE, - get_docs) + get_docs, + response_to_json_dict, + ) from .document import Document from .design_document import DesignDocument from .security_document import SecurityDocument @@ -123,7 +125,7 @@ def metadata(self): """ resp = self.r_session.get(self.database_url) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def doc_count(self): """ @@ -192,7 +194,7 @@ def design_documents(self): query = "startkey=\"_design\"&endkey=\"_design0\"&include_docs=true" resp = self.r_session.get(url, params=query) resp.raise_for_status() - data = resp.json() + data = response_to_json_dict(resp) return data['rows'] def list_design_documents(self): @@ -206,7 +208,7 @@ def list_design_documents(self): query = "startkey=\"_design\"&endkey=\"_design0\"" resp = self.r_session.get(url, params=query) resp.raise_for_status() - data = resp.json() + data = response_to_json_dict(resp) return [x.get('key') for x in data.get('rows', [])] def get_design_document(self, ddoc_id): @@ -403,7 +405,7 @@ def all_docs(self, **kwargs): '/'.join([self.database_url, '_all_docs']), self.client.encoder, **kwargs) - return resp.json() + return response_to_json_dict(resp) @contextlib.contextmanager def custom_result(self, **options): @@ -718,7 +720,7 @@ def bulk_docs(self, docs): headers=headers ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def missing_revisions(self, doc_id, *revisions): """ @@ -742,7 +744,7 @@ def missing_revisions(self, doc_id, *revisions): ) resp.raise_for_status() - resp_json = resp.json() + resp_json = response_to_json_dict(resp) missing_revs = resp_json['missing_revs'].get(doc_id) if missing_revs is None: missing_revs = [] @@ -771,7 +773,7 @@ def revisions_diff(self, doc_id, *revisions): ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def get_revision_limit(self): """ @@ -787,7 +789,7 @@ def get_revision_limit(self): try: ret = int(resp.text) except ValueError: - raise CloudantDatabaseException(400, resp.json()) + raise CloudantDatabaseException(400, response_to_json_dict(resp)) return ret @@ -806,7 +808,7 @@ def set_revision_limit(self, limit): resp = self.r_session.put(url, data=json.dumps(limit, cls=self.client.encoder)) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def view_cleanup(self): """ @@ -822,7 +824,7 @@ def view_cleanup(self): ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def get_list_function_result(self, ddoc_id, list_name, view_name, **kwargs): """ @@ -974,10 +976,10 @@ def get_query_indexes(self, raw_result=False): resp.raise_for_status() if raw_result: - return resp.json() + return response_to_json_dict(resp) indexes = [] - for data in resp.json().get('indexes', []): + for data in response_to_json_dict(resp).get('indexes', []): if data.get('type') == JSON_INDEX_TYPE: indexes.append(Index( self, @@ -1239,7 +1241,7 @@ def share_database(self, username, roles=None): headers={'Content-Type': 'application/json'} ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def unshare_database(self, username): """ @@ -1264,7 +1266,7 @@ def unshare_database(self, username): headers={'Content-Type': 'application/json'} ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def shards(self): """ @@ -1276,7 +1278,7 @@ def shards(self): resp = self.r_session.get(url) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def get_search_result(self, ddoc_id, index_name, **query_params): """ @@ -1399,4 +1401,4 @@ def get_search_result(self, ddoc_id, index_name, **query_params): data=json.dumps(query_params, cls=self.client.encoder) ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) diff --git a/src/cloudant/design_document.py b/src/cloudant/design_document.py index b32c5e33..66a9789d 100644 --- a/src/cloudant/design_document.py +++ b/src/cloudant/design_document.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2015 IBM. All rights reserved. +# Copyright (C) 2015, 2018 IBM. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ API module/class for interacting with a design document in a database. """ from ._2to3 import iteritems_, STRTYPE -from ._common_util import QUERY_LANGUAGE, codify +from ._common_util import QUERY_LANGUAGE, codify, response_to_json_dict from .document import Document from .view import View, QueryIndexView from .error import CloudantArgumentError, CloudantDesignDocumentException @@ -686,7 +686,7 @@ def info(self): ddoc_info = self.r_session.get( '/'.join([self.document_url, '_info'])) ddoc_info.raise_for_status() - return ddoc_info.json() + return response_to_json_dict(ddoc_info) def search_info(self, search_index): """ @@ -698,7 +698,7 @@ def search_info(self, search_index): ddoc_search_info = self.r_session.get( '/'.join([self.document_url, '_search_info', search_index])) ddoc_search_info.raise_for_status() - return ddoc_search_info.json() + return response_to_json_dict(ddoc_search_info) def search_disk_size(self, search_index): """ @@ -710,4 +710,4 @@ def search_disk_size(self, search_index): ddoc_search_disk_size = self.r_session.get( '/'.join([self.document_url, '_search_disk_size', search_index])) ddoc_search_disk_size.raise_for_status() - return ddoc_search_disk_size.json() + return response_to_json_dict(ddoc_search_disk_size) diff --git a/src/cloudant/document.py b/src/cloudant/document.py index ac08ee65..426b0656 100644 --- a/src/cloudant/document.py +++ b/src/cloudant/document.py @@ -20,6 +20,7 @@ from requests.exceptions import HTTPError from ._2to3 import url_quote, url_quote_plus +from ._common_util import response_to_json_dict from .error import CloudantDocumentException @@ -53,8 +54,8 @@ class Document(dict): :param database: A database instance used by the Document. Can be either a ``CouchDatabase`` or ``CloudantDatabase`` instance. :param str document_id: Optional document id used to identify the document. - :param str encoder: Optional JSON encoder object. - :param str decoder: Optional JSON decoder object. + :param str encoder: Optional JSON encoder object (extending json.JSONEncoder). + :param str decoder: Optional JSON decoder object (extending json.JSONDecoder). """ def __init__(self, database, document_id=None, **kwargs): super(Document, self).__init__() @@ -150,7 +151,7 @@ def create(self): data=json.dumps(doc, cls=self.encoder) ) resp.raise_for_status() - data = resp.json() + data = response_to_json_dict(resp) self._document_id = data['id'] super(Document, self).__setitem__('_id', data['id']) super(Document, self).__setitem__('_rev', data['rev']) @@ -167,7 +168,7 @@ def fetch(self): resp = self.r_session.get(self.document_url) resp.raise_for_status() self.clear() - self.update(resp.json(cls=self.decoder)) + self.update(response_to_json_dict(resp, cls=self.decoder)) def save(self): """ @@ -189,7 +190,7 @@ def save(self): headers=headers ) put_resp.raise_for_status() - data = put_resp.json() + data = response_to_json_dict(put_resp) super(Document, self).__setitem__('_rev', data['rev']) return @@ -418,7 +419,7 @@ def get_attachment( if attachment_type == 'text': return resp.text if attachment_type == 'json': - return resp.json() + return response_to_json_dict(resp) return resp.content @@ -447,7 +448,7 @@ def delete_attachment(self, attachment, headers=None): headers=headers ) resp.raise_for_status() - super(Document, self).__setitem__('_rev', resp.json()['rev']) + super(Document, self).__setitem__('_rev', response_to_json_dict(resp)['rev']) # Execute logic only if attachment metadata exists locally if self.get('_attachments'): # Remove the attachment metadata for the specified attachment @@ -457,7 +458,7 @@ def delete_attachment(self, attachment, headers=None): if not self['_attachments']: super(Document, self).__delitem__('_attachments') - return resp.json() + return response_to_json_dict(resp) def put_attachment(self, attachment, content_type, data, headers=None): """ @@ -494,4 +495,4 @@ def put_attachment(self, attachment, content_type, data, headers=None): ) resp.raise_for_status() self.fetch() - return resp.json() + return response_to_json_dict(resp) diff --git a/src/cloudant/index.py b/src/cloudant/index.py index 3d0d49c3..40274ce1 100644 --- a/src/cloudant/index.py +++ b/src/cloudant/index.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2015 IBM. All rights reserved. +# Copyright (C) 2015, 2018 IBM. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ from ._common_util import TEXT_INDEX_TYPE from ._common_util import SPECIAL_INDEX_TYPE from ._common_util import TEXT_INDEX_ARGS +from ._common_util import response_to_json_dict from .error import CloudantArgumentError, CloudantIndexException class Index(object): @@ -143,8 +144,8 @@ def create(self): headers=headers ) resp.raise_for_status() - self._ddoc_id = resp.json()['id'] - self._name = resp.json()['name'] + self._ddoc_id = response_to_json_dict(resp)['id'] + self._name = response_to_json_dict(resp)['name'] def _def_check(self): """ diff --git a/src/cloudant/query.py b/src/cloudant/query.py index b5bd2b58..ed9ec35e 100644 --- a/src/cloudant/query.py +++ b/src/cloudant/query.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2015 IBM. All rights reserved. +# Copyright (C) 2015, 2018 IBM. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ from .result import QueryResult from .error import CloudantArgumentError from ._common_util import QUERY_ARG_TYPES +from ._common_util import response_to_json_dict class Query(dict): """ @@ -172,7 +173,7 @@ def __call__(self, **kwargs): data=json.dumps(data, cls=self._encoder) ) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) @contextlib.contextmanager def custom_result(self, **options): diff --git a/src/cloudant/scheduler.py b/src/cloudant/scheduler.py index 723635b2..012272fe 100644 --- a/src/cloudant/scheduler.py +++ b/src/cloudant/scheduler.py @@ -16,6 +16,8 @@ API module for interacting with scheduler endpoints """ +from ._common_util import response_to_json_dict + class Scheduler(object): """ API for retrieving scheduler jobs and documents. @@ -47,7 +49,7 @@ def list_docs(self, limit=None, skip=None): params["skip"] = skip resp = self._r_session.get('/'.join([self._scheduler, 'docs']), params=params) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def get_doc(self, doc_id): """ @@ -55,7 +57,7 @@ def get_doc(self, doc_id): """ resp = self._r_session.get('/'.join([self._scheduler, 'docs', '_replicator', doc_id])) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) def list_jobs(self, limit=None, skip=None): @@ -78,4 +80,4 @@ def list_jobs(self, limit=None, skip=None): params["skip"] = skip resp = self._r_session.get('/'.join([self._scheduler, 'jobs']), params=params) resp.raise_for_status() - return resp.json() + return response_to_json_dict(resp) diff --git a/src/cloudant/security_document.py b/src/cloudant/security_document.py index 22b0f79c..87b77ca3 100644 --- a/src/cloudant/security_document.py +++ b/src/cloudant/security_document.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2016 IBM. All rights reserved. +# Copyright (C) 2016, 2018 IBM. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import json from ._2to3 import url_quote_plus +from ._common_util import response_to_json_dict class SecurityDocument(dict): """ @@ -101,7 +102,7 @@ def fetch(self): resp = self.r_session.get(self.document_url) resp.raise_for_status() self.clear() - self.update(resp.json()) + self.update(response_to_json_dict(resp)) def save(self): """ diff --git a/src/cloudant/view.py b/src/cloudant/view.py index 685600e5..3a0e63fc 100644 --- a/src/cloudant/view.py +++ b/src/cloudant/view.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2015 IBM. All rights reserved. +# Copyright (C) 2015, 2018 IBM. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import contextlib from ._2to3 import STRTYPE -from ._common_util import codify, get_docs +from ._common_util import codify, get_docs, response_to_json_dict from .result import Result from .error import CloudantArgumentError, CloudantViewException @@ -227,7 +227,7 @@ def __call__(self, **kwargs): self.url, self.design_doc.encoder, **kwargs) - return resp.json() + return response_to_json_dict(resp) @contextlib.contextmanager def custom_result(self, **options): diff --git a/tests/unit/client_tests.py b/tests/unit/client_tests.py index e110e5fa..0a544a15 100644 --- a/tests/unit/client_tests.py +++ b/tests/unit/client_tests.py @@ -247,7 +247,7 @@ def test_session_basic(self, m_req): """ m_response_ok = mock.MagicMock() type(m_response_ok).status_code = mock.PropertyMock(return_value=200) - m_response_ok.json.return_value = ['animaldb'] + type(m_response_ok).text = mock.PropertyMock(return_value='["animaldb"]') m_req.return_value = m_response_ok client = Cloudant('foo', 'bar', url=self.url, use_basic_auth=True) @@ -298,8 +298,7 @@ def test_change_credentials_basic(self, m_req): """ # mock 200 m_response_ok = mock.MagicMock() - m_response_ok.json.return_value = ['animaldb'] - + type(m_response_ok).text = mock.PropertyMock(return_value='["animaldb"]') # mock 401 m_response_bad = mock.MagicMock() m_response_bad.raise_for_status.side_effect = HTTPError('401 Unauthorized') diff --git a/tests/unit/database_tests.py b/tests/unit/database_tests.py index 5881a92d..f4c0e9aa 100644 --- a/tests/unit/database_tests.py +++ b/tests/unit/database_tests.py @@ -29,6 +29,7 @@ import mock import requests from cloudant._2to3 import UNICHR +from cloudant._common_util import response_to_json_dict from cloudant.design_document import DesignDocument from cloudant.document import Document from cloudant.error import CloudantArgumentError, CloudantDatabaseException @@ -237,7 +238,7 @@ def test_retrieve_db_metadata(self): """ resp = self.db.r_session.get( '/'.join((self.client.server_url, self.test_dbname))) - expected = resp.json() + expected = response_to_json_dict(resp) actual = self.db.metadata() self.assertListEqual(list(actual.keys()), list(expected.keys())) diff --git a/tests/unit/design_document_tests.py b/tests/unit/design_document_tests.py index 43714727..c986e769 100644 --- a/tests/unit/design_document_tests.py +++ b/tests/unit/design_document_tests.py @@ -27,6 +27,7 @@ import mock import requests +from cloudant._common_util import response_to_json_dict from cloudant.design_document import DesignDocument from cloudant.document import Document from cloudant.error import CloudantArgumentError, CloudantDesignDocumentException @@ -693,7 +694,7 @@ def test_save_with_no_views(self): # Ensure that remotely saved design document does not # include a views sub-document. resp = self.client.r_session.get(ddoc.document_url) - raw_ddoc = resp.json() + raw_ddoc = response_to_json_dict(resp) self.assertEqual(set(raw_ddoc.keys()), {'_id', '_rev'}) self.assertEqual(raw_ddoc['_id'], ddoc['_id']) self.assertEqual(raw_ddoc['_rev'], ddoc['_rev']) @@ -1182,7 +1183,7 @@ def test_save_with_no_search_indexes(self): # Ensure that remotely saved design document does not # include a search indexes sub-document. resp = self.client.r_session.get(ddoc.document_url) - raw_ddoc = resp.json() + raw_ddoc = response_to_json_dict(resp) self.assertEqual(set(raw_ddoc.keys()), {'_id', '_rev'}) self.assertEqual(raw_ddoc['_id'], ddoc['_id']) self.assertEqual(raw_ddoc['_rev'], ddoc['_rev']) @@ -1263,7 +1264,7 @@ def test_rewrite_rule(self): doc.save() resp = self.client.r_session.get('/'.join([ddoc.document_url, '_rewrite'])) self.assertEquals( - resp.json(), + response_to_json_dict(resp), { '_id': 'rewrite_doc', '_rev': doc['_rev'] @@ -1451,7 +1452,7 @@ def test_save_with_no_list_functions(self): # Ensure that remotely saved design document does not # include a lists sub-document. resp = self.client.r_session.get(ddoc.document_url) - raw_ddoc = resp.json() + raw_ddoc = response_to_json_dict(resp) self.assertEqual(set(raw_ddoc.keys()), {'_id', '_rev'}) self.assertEqual(raw_ddoc['_id'], ddoc['_id']) self.assertEqual(raw_ddoc['_rev'], ddoc['_rev']) @@ -1752,7 +1753,7 @@ def test_save_with_no_show_functions(self): # Ensure that remotely saved design document does not # include a shows sub-document. resp = self.client.r_session.get(ddoc.document_url) - raw_ddoc = resp.json() + raw_ddoc = response_to_json_dict(resp) self.assertEqual(set(raw_ddoc.keys()), {'_id', '_rev'}) self.assertEqual(raw_ddoc['_id'], ddoc['_id']) self.assertEqual(raw_ddoc['_rev'], ddoc['_rev']) @@ -1834,7 +1835,7 @@ def test_update_validator(self): data=json.dumps({'_id': 'test001'}) ) self.assertEqual( - resp.json(), + response_to_json_dict(resp), {'reason': 'Document must have an address.', 'error': 'forbidden'} ) diff --git a/tests/unit/iam_auth_tests.py b/tests/unit/iam_auth_tests.py index 6e0c7670..ec4fbd95 100644 --- a/tests/unit/iam_auth_tests.py +++ b/tests/unit/iam_auth_tests.py @@ -43,24 +43,22 @@ '2PTo4Exa17V-R_73Nq8VPCwpOvZcwKRA2sPTVgTMzU34max8b5kpTzVGJ' '6SXSItTVOUdAygZBng') -MOCK_IAM_TOKEN_RESPONSE = { - 'access_token': MOCK_ACCESS_TOKEN, - 'refresh_token': ('MO61FKNvVRWkSa4vmBZqYv_Jt1kkGMUc-XzTcNnR-GnIhVKXHUWxJVV3' - 'RddE8Kqh3X_TZRmyK8UySIWKxoJ2t6obUSUalPm90SBpTdoXtaljpNyo' - 'rmqCCYPROnk6JBym72ikSJqKHHEZVQkT0B5ggZCwPMnKagFj0ufs-VIh' - 'CF97xhDxDKcIPMWG02xxPuESaSTJJug7e_dUDoak_ZXm9xxBmOTRKwOx' - 'n5sTKthNyvVpEYPE7jIHeiRdVDOWhN5LomgCn3TqFCLpMErnqwgNYbyC' - 'Bd9rNm-alYKDb6Jle4njuIBpXxQPb4euDwLd1osApaSME3nEarFWqRBz' - 'hjoqCe1Kv564s_rY7qzD1nHGvKOdpSa0ZkMcfJ0LbXSQPs7gBTSVrBFZ' - 'qwlg-2F-U3Cto62-9qRR_cEu_K9ZyVwL4jWgOlngKmxV6Ku4L5mHp4Kg' - 'EJSnY_78_V2nm64E--i2ZA1FhiKwIVHDOivVNhggE9oabxg54vd63glp' - '4GfpNnmZsMOUYG9blJJpH4fDX4Ifjbw-iNBD7S2LRpP8b8vG9pb4WioG' - 'zN43lE5CysveKYWrQEZpThznxXlw1snDu_A48JiL3Lrvo1LobLhF3zFV' - '-kQ='), - 'token_type': 'Bearer', - 'expires_in': 3600, # 60mins - 'expiration': 1500470702 # Wed Jul 19 14:25:02 2017 -} +MOCK_IAM_TOKEN_RESPONSE = '{"access_token": "%s",\ + "refresh_token": "MO61FKNvVRWkSa4vmBZqYv_Jt1kkGMUc-XzTcNnR-GnIhVKXHUWxJVV3\ + RddE8Kqh3X_TZRmyK8UySIWKxoJ2t6obUSUalPm90SBpTdoXtaljpNyo\ + rmqCCYPROnk6JBym72ikSJqKHHEZVQkT0B5ggZCwPMnKagFj0ufs-VIh\ + CF97xhDxDKcIPMWG02xxPuESaSTJJug7e_dUDoak_ZXm9xxBmOTRKwOx\ + n5sTKthNyvVpEYPE7jIHeiRdVDOWhN5LomgCn3TqFCLpMErnqwgNYbyC\ + Bd9rNm-alYKDb6Jle4njuIBpXxQPb4euDwLd1osApaSME3nEarFWqRBz\ + hjoqCe1Kv564s_rY7qzD1nHGvKOdpSa0ZkMcfJ0LbXSQPs7gBTSVrBFZ\ + qwlg-2F-U3Cto62-9qRR_cEu_K9ZyVwL4jWgOlngKmxV6Ku4L5mHp4Kg\ + EJSnY_78_V2nm64E--i2ZA1FhiKwIVHDOivVNhggE9oabxg54vd63glp\ + 4GfpNnmZsMOUYG9blJJpH4fDX4Ifjbw-iNBD7S2LRpP8b8vG9pb4WioG\ + zN43lE5CysveKYWrQEZpThznxXlw1snDu_A48JiL3Lrvo1LobLhF3zFV\ + -kQ=",\ + "token_type": "Bearer",\ + "expires_in": 3600,\ + "expiration": 1500470702}'%(MOCK_ACCESS_TOKEN) class IAMAuthTests(unittest.TestCase): @@ -100,7 +98,8 @@ def test_iam_set_credentials(self): @mock.patch('cloudant._client_session.ClientSession.request') def test_iam_get_access_token(self, m_req): m_response = mock.MagicMock() - m_response.json.return_value = MOCK_IAM_TOKEN_RESPONSE + mock_token_response_text = mock.PropertyMock(return_value=MOCK_IAM_TOKEN_RESPONSE) + type(m_response).text = mock_token_response_text m_req.return_value = m_response iam = IAMSession(MOCK_API_KEY, 'http://127.0.0.1:5984') @@ -120,7 +119,7 @@ def test_iam_get_access_token(self, m_req): self.assertEqual(access_token, MOCK_ACCESS_TOKEN) self.assertTrue(m_response.raise_for_status.called) - self.assertTrue(m_response.json.called) + mock_token_response_text.assert_called_with() @mock.patch('cloudant._client_session.ClientSession.request') @mock.patch('cloudant._client_session.IAMSession._get_access_token') @@ -152,10 +151,10 @@ def test_iam_logout(self): @mock.patch('cloudant._client_session.ClientSession.get') def test_iam_get_session_info(self, m_get): - m_info = {'ok': True, 'info': {'authentication_db': '_users'}} + m_info = '{"ok": true, "info": {"authentication_db": "_users"}}' m_response = mock.MagicMock() - m_response.json.return_value = m_info + type(m_response).text = mock.PropertyMock(return_value=m_info) m_get.return_value = m_response iam = IAMSession(MOCK_API_KEY, 'http://127.0.0.1:5984') @@ -163,7 +162,7 @@ def test_iam_get_session_info(self, m_get): m_get.assert_called_once_with(iam._session_url) - self.assertEqual(info, m_info) + self.assertEqual(info, json.loads(m_info)) self.assertTrue(m_response.raise_for_status.called) @mock.patch('cloudant._client_session.IAMSession.login') @@ -172,7 +171,7 @@ def test_iam_first_request(self, m_req, m_login): # mock 200 m_response_ok = mock.MagicMock() type(m_response_ok).status_code = mock.PropertyMock(return_value=200) - m_response_ok.json.return_value = {'ok': True} + type(m_response_ok).text = mock.PropertyMock(return_value='{"ok": true}') m_req.return_value = m_response_ok @@ -197,7 +196,7 @@ def test_iam_renew_cookie_on_expiry(self, m_req, m_login): # mock 200 m_response_ok = mock.MagicMock() type(m_response_ok).status_code = mock.PropertyMock(return_value=200) - m_response_ok.json.return_value = {'ok': True} + type(m_response_ok).text = mock.PropertyMock(return_value='{"ok": true}') m_req.return_value = m_response_ok @@ -219,7 +218,7 @@ def test_iam_renew_cookie_on_401_success(self, m_req, m_login): # mock 200 m_response_ok = mock.MagicMock() type(m_response_ok).status_code = mock.PropertyMock(return_value=200) - m_response_ok.json.return_value = {'ok': True} + type(m_response_ok).text = mock.PropertyMock(return_value='{"ok": true}') # mock 401 m_response_bad = mock.MagicMock() type(m_response_bad).status_code = mock.PropertyMock(return_value=401) @@ -298,7 +297,7 @@ def test_iam_client_create(self, m_req, m_login): # mock 200 m_response_ok = mock.MagicMock() type(m_response_ok).status_code = mock.PropertyMock(return_value=200) - m_response_ok.json.return_value = ['animaldb'] + type(m_response_ok).text = mock.PropertyMock(return_value='["animaldb"]') m_req.return_value = m_response_ok diff --git a/tests/unit/scheduler_tests.py b/tests/unit/scheduler_tests.py index 97b5046f..5ef8cab9 100644 --- a/tests/unit/scheduler_tests.py +++ b/tests/unit/scheduler_tests.py @@ -46,107 +46,105 @@ def test_scheduler_docs(self): Test scheduler docs """ # set up mock response using a real captured response - m_response_ok = requests.Response() - m_response_ok.status_code = 200 - m_response_ok.json = mock.Mock() - m_response_ok.json.return_value = {"total_rows":6,"offset":0,"docs":[ - {"database":"tomblench/_replicator", - "doc_id":"296e48244e003eba8764b2156b3bf302", - "id":None, - "source":"https://tomblench.cloudant.com/animaldb/", - "target":"https://tomblench.cloudant.com/animaldb_copy/", - "state":"completed", - "error_count":0, - "info":{"revisions_checked":15, - "missing_revisions_found":2, - "docs_read":2, - "docs_written":2, - "changes_pending":None, - "doc_write_failures":0, - "checkpointed_source_seq":"19-g1AAAAGjeJyVz10KwjAMB_BoJ4KX8AZF2tWPJ3eVpqnO0XUg27PeTG9Wa_VhwmT6kkDIPz_iACArGcGS0DRnWxDmHE9HdJ3lxjUdad9yb1sXF6cacB9CqEqmZ3UczKUh2uGhHxeD8U9i_Z3AIla8vJVJUlBIZYTqX5A_KMM7SfFZrHCNLUK3p7RIkl5tSRD-K6kx6f6S0k8sScpYJTb5uFQ9AI9Ch9c"}, - "start_time":None, - "last_updated":"2017-04-13T14:53:50+00:00"}, - {"database":"tomblench/_replicator", - "doc_id":"3b749f320867d703550b0f758a4000ae", - "id":None, - "source":"https://examples.cloudant.com/animaldb/", - "target":"https://tomblench.cloudant.com/animaldb/", - "state":"completed", - "error_count":0, - "info":{"revisions_checked":15, - "missing_revisions_found":15, - "docs_read":15, - "docs_written":15, - "changes_pending":None, - "doc_write_failures":0, - "checkpointed_source_seq":"56-g1AAAAGveJzLYWBgYMlgTmFQSElKzi9KdUhJstDLTS3KLElMT9VLzskvTUnMK9HLSy3JAapkSmRIsv___39WBnMiby5QgN04JS3FLDUJWb8Jdv0gSxThigyN8diS5AAkk-qhFvFALEo2MTEwMSXGDDSbTPHYlMcCJBkagBTQsv0g28TBtpkbGCQapaF4C4cxJFt2AGIZ2GscYMuMDEzMUizMkC0zw25MFgBKoovi"}, - "start_time":None, - "last_updated":"2017-04-27T12:28:44+00:00"}, - {"database":"tomblench/_replicator", - "doc_id":"ad8f7896480b8081c8f0a2267ffd1859", - "id":None, - "source":"https://tortytherlediffecareette:*****@mikerhodestesty008.cloudant.com/moviesdb/", - "target":"https://tomblench.cloudant.com/moviesdb_rep/", - "state":"completed", - "error_count":0, - "info":{"revisions_checked":5997, - "missing_revisions_found":5997, - "docs_read":5997, - "docs_written":5997, - "changes_pending":None, - "doc_write_failures":0, - "checkpointed_source_seq":"5997-g1AAAANreJy10UEKwjAQAMBgBcVP2BeUpEm1PdmfaDYJSKkVtB486U_0J_oBTz5AHyAI3jxIjUml1x7ayy67LDssmyKE-nNHIleCWK5ULIF6uVrnW4xDT6TLjeRZ7mUqT_VkhyMYFkWRzB3Q1XOhez3iczKKghor6jvg6giTiroYiuNQYYqbpeIfNa2oh72KhQGosFlq9qN2FfUyFPgUCKONoneXR7TXSWuHkvsYjjEWjQVvgTta7lRyV_szKgmRbVx3ttzNcs7AcEoKCHAb3N1y_9-9DYeBYzEiNTYlX3EcE0s"}, - "start_time":None, - "last_updated":"2016-08-23T13:11:26+00:00"}, - {"database":"tomblench/_replicator", - "doc_id":"b63c053ecd95a4047b55ed8847b046f1", - "id":None, - "source":"https://tomblench.cloudant.com/atestdb2/", - "target":"https://tomblench.cloudant.com/atestdb1/", - "state":"completed", - "error_count":0, - "info":{"revisions_checked":1, - "missing_revisions_found":1, - "docs_read":1, - "docs_written":1, - "changes_pending":None, - "doc_write_failures":0, - "checkpointed_source_seq":"2-g1AAAAFHeJyNjkEOgjAQRSdAYjyFN2jSFCtdyVU6nSKQWhJC13ozvVktsoEF0c2fTPL_-98BQNHmBCdCM4y2JuQMuxu6YJlxQyDtJ-bt5JIx04DXGGOvYRsR-xGsk-JjTrW5hnv6Dg0XplRngmPwZJvOW9ry5D7PF0nhmU5CvmZm9mVKVVacLr8pfy9fmt5L02q9qEhJbtbr-w-AQmfD"}, - "start_time":None, - "last_updated":"2017-05-16T16:25:22+00:00"}, - {"database":"tomblench/_replicator", - "doc_id":"c71c9e69e30a182dc91d8938277bc85e", - "id":None, - "source":"https://tomblench.cloudant.com/animaldb/", - "target":"https://tomblench.cloudant.com/animaldb_copy/", - "state":"completed", - "error_count":0, - "info":{"revisions_checked":15, - "missing_revisions_found":15, - "docs_read":15, - "docs_written":15, - "changes_pending":None, - "doc_write_failures":0, - "checkpointed_source_seq":"14-g1AAAAEueJzLYWBgYMlgTmGQSUlKzi9KdUhJMtTLTU1M0UvOyS9NScwr0ctLLckBqmJKZEiy____f1YGUyJrLlCAPdHEPCktJZk43UkOQDKpHmoAI9gAw2STxCTzJOIMyGMBkgwNQApoxv6sDGaoK0yN04wsk80IGEGKHQcgdoAdygxxaIplklFaWhYAu2FdOA"}, - "start_time":None, - "last_updated":"2015-05-12T11:47:33+00:00"}, - {"database":"tomblench/_replicator", - "doc_id":"e6242d1e9ce059b0388fc75af3116a39", - "id":None, - "source":"https://tomblench.cloudant.com/atestdb1/", - "target":"https://tomblench.cloudant.com/atestdb2/", - "state":"completed", - "error_count":0, - "info":{"revisions_checked":1, - "missing_revisions_found":1, - "docs_read":1, - "docs_written":1, - "changes_pending":None, - "doc_write_failures":0, - "checkpointed_source_seq":"1-g1AAAAFheJyFzkEOgjAQBdBRSIyn8AZNgEJgJVeZ6bQCqSUhdK0305th1Q1dEDYzyWTy_rcAkHYJw4VJjZNumQpB_Y2s10LZ0TO6WTg92_B4RKDrsixDlyDcw-FUVUiFahjO3rE2vdMcY9k2Rm2Y9Ig8bWqspdz25Lbn0jDhGVYgX1_z8DMblnlp8n0lTir3kt7_pFV7NE2WYbluP3wATr5vQA"}, - "start_time":None, - "last_updated":"2017-05-16T16:24:02+00:00"} - ]} + m_response_ok = mock.MagicMock() + type(m_response_ok).status_code = mock.PropertyMock(return_value=200) + type(m_response_ok).text = mock.PropertyMock(return_value='{"total_rows":6,"offset":0,"docs":[\ + {"database":"tomblench/_replicator",\ + "doc_id":"296e48244e003eba8764b2156b3bf302",\ + "id":null,\ + "source":"https://tomblench.cloudant.com/animaldb/",\ + "target":"https://tomblench.cloudant.com/animaldb_copy/",\ + "state":"completed",\ + "error_count":0,\ + "info":{"revisions_checked":15,\ + "missing_revisions_found":2,\ + "docs_read":2,\ + "docs_written":2,\ + "changes_pending":null,\ + "doc_write_failures":0,\ + "checkpointed_source_seq":"19-g1AAAAGjeJyVz10KwjAMB_BoJ4KX8AZF2tWPJ3eVpqnO0XUg27PeTG9Wa_VhwmT6kkDIPz_iACArGcGS0DRnWxDmHE9HdJ3lxjUdad9yb1sXF6cacB9CqEqmZ3UczKUh2uGhHxeD8U9i_Z3AIla8vJVJUlBIZYTqX5A_KMM7SfFZrHCNLUK3p7RIkl5tSRD-K6kx6f6S0k8sScpYJTb5uFQ9AI9Ch9c"},\ + "start_time":null,\ + "last_updated":"2017-04-13T14:53:50+00:00"},\ + {"database":"tomblench/_replicator",\ + "doc_id":"3b749f320867d703550b0f758a4000ae",\ + "id":null,\ + "source":"https://examples.cloudant.com/animaldb/",\ + "target":"https://tomblench.cloudant.com/animaldb/",\ + "state":"completed",\ + "error_count":0,\ + "info":{"revisions_checked":15,\ + "missing_revisions_found":15,\ + "docs_read":15,\ + "docs_written":15,\ + "changes_pending":null,\ + "doc_write_failures":0,\ + "checkpointed_source_seq":"56-g1AAAAGveJzLYWBgYMlgTmFQSElKzi9KdUhJstDLTS3KLElMT9VLzskvTUnMK9HLSy3JAapkSmRIsv___39WBnMiby5QgN04JS3FLDUJWb8Jdv0gSxThigyN8diS5AAkk-qhFvFALEo2MTEwMSXGDDSbTPHYlMcCJBkagBTQsv0g28TBtpkbGCQapaF4C4cxJFt2AGIZ2GscYMuMDEzMUizMkC0zw25MFgBKoovi"},\ + "start_time":null,\ + "last_updated":"2017-04-27T12:28:44+00:00"},\ + {"database":"tomblench/_replicator",\ + "doc_id":"ad8f7896480b8081c8f0a2267ffd1859",\ + "id":null,\ + "source":"https://tortytherlediffecareette:*****@mikerhodestesty008.cloudant.com/moviesdb/",\ + "target":"https://tomblench.cloudant.com/moviesdb_rep/",\ + "state":"completed",\ + "error_count":0,\ + "info":{"revisions_checked":5997,\ + "missing_revisions_found":5997,\ + "docs_read":5997,\ + "docs_written":5997,\ + "changes_pending":null,\ + "doc_write_failures":0,\ + "checkpointed_source_seq":"5997-g1AAAANreJy10UEKwjAQAMBgBcVP2BeUpEm1PdmfaDYJSKkVtB486U_0J_oBTz5AHyAI3jxIjUml1x7ayy67LDssmyKE-nNHIleCWK5ULIF6uVrnW4xDT6TLjeRZ7mUqT_VkhyMYFkWRzB3Q1XOhez3iczKKghor6jvg6giTiroYiuNQYYqbpeIfNa2oh72KhQGosFlq9qN2FfUyFPgUCKOnullXR7TXSWuHkvsYjjEWjQVvgTta7lRyV_szKgmRbVx3ttzNcs7AcEoKCHAb3N1y_9-9DYeBYzEiNTYlX3EcE0s"},\ + "start_time":null,\ + "last_updated":"2016-08-23T13:11:26+00:00"},\ + {"database":"tomblench/_replicator",\ + "doc_id":"b63c053ecd95a4047b55ed8847b046f1",\ + "id":null,\ + "source":"https://tomblench.cloudant.com/atestdb2/",\ + "target":"https://tomblench.cloudant.com/atestdb1/",\ + "state":"completed",\ + "error_count":0,\ + "info":{"revisions_checked":1,\ + "missing_revisions_found":1,\ + "docs_read":1,\ + "docs_written":1,\ + "changes_pending":null,\ + "doc_write_failures":0,\ + "checkpointed_source_seq":"2-g1AAAAFHeJyNjkEOgjAQRSdAYjyFN2jSFCtdyVU6nSKQWhJC13ozvVktsoEF0c2fTPL_-98BQNHmBCdCM4y2JuQMuxu6YJlxQyDtJ-bt5JIx04DXGGOvYRsR-xGsk-JjTrW5hnv6Dg0XplRngmPwZJvOW9ry5D7PF0nhmU5CvmZm9mVKVVacLr8pfy9fmt5L02q9qEhJbtbr-w-AQmfD"},\ + "start_time":null,\ + "last_updated":"2017-05-16T16:25:22+00:00"},\ + {"database":"tomblench/_replicator",\ + "doc_id":"c71c9e69e30a182dc91d8938277bc85e",\ + "id":null,\ + "source":"https://tomblench.cloudant.com/animaldb/",\ + "target":"https://tomblench.cloudant.com/animaldb_copy/",\ + "state":"completed",\ + "error_count":0,\ + "info":{"revisions_checked":15,\ + "missing_revisions_found":15,\ + "docs_read":15,\ + "docs_written":15,\ + "changes_pending":null,\ + "doc_write_failures":0,\ + "checkpointed_source_seq":"14-g1AAAAEueJzLYWBgYMlgTmGQSUlKzi9KdUhJMtTLTU1M0UvOyS9NScwr0ctLLckBqmJKZEiy____f1YGUyJrLlCAPdHEPCktJZk43UkOQDKpHmoAI9gAw2STxCTzJOIMyGMBkgwNQApoxv6sDGaoK0yN04wsk80IGEGKHQcgdoAdygxxaIplklFaWhYAu2FdOA"},\ + "start_time":null,\ + "last_updated":"2015-05-12T11:47:33+00:00"},\ + {"database":"tomblench/_replicator",\ + "doc_id":"e6242d1e9ce059b0388fc75af3116a39",\ + "id":null,\ + "source":"https://tomblench.cloudant.com/atestdb1/",\ + "target":"https://tomblench.cloudant.com/atestdb2/",\ + "state":"completed",\ + "error_count":0,\ + "info":{"revisions_checked":1,\ + "missing_revisions_found":1,\ + "docs_read":1,\ + "docs_written":1,\ + "changes_pending":null,\ + "doc_write_failures":0,\ + "checkpointed_source_seq":"1-g1AAAAFheJyFzkEOgjAQBdBRSIyn8AZNgEJgJVeZ6bQCqSUhdK0305th1Q1dEDYzyWTy_rcAkHYJw4VJjZNumQpB_Y2s10LZ0TO6WTg92_B4RKDrsixDlyDcw-FUVUiFahjO3rE2vdMcY9k2Rm2Y9Ig8bWqspdz25Lbn0jDhGVYgX1_z8DMblnlp8n0lTir3kt7_pFV7NE2WYbluP3wATr5vQA"},\ + "start_time":null,\ + "last_updated":"2017-05-16T16:24:02+00:00"}]}') self.client.r_session.get = mock.Mock(return_value=m_response_ok) scheduler = Scheduler(self.client) @@ -163,25 +161,24 @@ def test_scheduler_doc(self): Test scheduler doc """ # set up mock response using a real captured response - m_response_ok = requests.Response() - m_response_ok.status_code = 200 - m_response_ok.json = mock.Mock() - m_response_ok.json.return_value = {"database":"tomblench/_replicator", - "doc_id":"296e48244e003eba8764b2156b3bf302", - "id":None, - "source":"https://tomblench.cloudant.com/animaldb/", - "target":"https://tomblench.cloudant.com/animaldb_copy/", - "state":"completed", - "error_count":0, - "info":{"revisions_checked":15, - "missing_revisions_found":2, - "docs_read":2, - "docs_written":2, - "changes_pending":None, - "doc_write_failures":0, - "checkpointed_source_seq":"19-g1AAAAGjeJyVz10KwjAMB_BoJ4KX8AZF2tWPJ3eVpqnO0XUg27PeTG9Wa_VhwmT6kkDIPz_iACArGcGS0DRnWxDmHE9HdJ3lxjUdad9yb1sXF6cacB9CqEqmZ3UczKUh2uGhHxeD8U9i_Z3AIla8vJVJUlBIZYTqX5A_KMM7SfFZrHCNLUK3p7RIkl5tSRD-K6kx6f6S0k8sScpYJTb5uFQ9AI9Ch9c"}, - "start_time":None, - "last_updated":"2017-04-13T14:53:50+00:00"}; + m_response_ok = mock.MagicMock() + type(m_response_ok).status_code = mock.PropertyMock(return_value=200) + type(m_response_ok).text = mock.PropertyMock(return_value='{"database":"tomblench/_replicator",\ + "doc_id":"296e48244e003eba8764b2156b3bf302",\ + "id":null,\ + "source":"https://tomblench.cloudant.com/animaldb/",\ + "target":"https://tomblench.cloudant.com/animaldb_copy/",\ + "state":"completed",\ + "error_count":0,\ + "info":{"revisions_checked":15,\ + "missing_revisions_found":2,\ + "docs_read":2,\ + "docs_written":2,\ + "changes_pending":null,\ + "doc_write_failures":0,\ + "checkpointed_source_seq":"19-g1AAAAGjeJyVz10KwjAMB_BoJ4KX8AZF2tWPJ3eVpqnO0XUg27PeTG9Wa_VhwmT6kkDIPz_iACArGcGS0DRnWxDmHE9HdJ3lxjUdad9yb1sXF6cacB9CqEqmZ3UczKUh2uGhHxeD8U9i_Z3AIla8vJVJUlBIZYTqX5A_KMM7SfFZrHCNLUK3p7RIkl5tSRD-K6kx6f6S0k8sScpYJTb5uFQ9AI9Ch9c"},\ + "start_time":null,\ + "last_updated":"2017-04-13T14:53:50+00:00"}') self.client.r_session.get = mock.Mock(return_value=m_response_ok) scheduler = Scheduler(self.client) response = scheduler.get_doc("296e48244e003eba8764b2156b3bf302") @@ -197,23 +194,22 @@ def test_scheduler_jobs(self): Test scheduler jobs """ # set up mock response using a real captured response - m_response_ok = requests.Response() - m_response_ok.status_code = 200 - m_response_ok.json = mock.Mock() - m_response_ok.json.return_value = {"total_rows":1,"offset":0, - "jobs":[{"database":None, - "id":"f11105eaaded4981d21ff8ebf846f48b+create_target", - "pid":"<0.5866.6800>", - "source":"https://clientlibs-test:*****@clientlibs-test.cloudant.com/largedb1g/", - "target":"https://tomblench:*****@tomblench.cloudant.com/largedb1g/", - "user":"tomblench", - "doc_id":None, - "history":[{"timestamp":"2018-04-12T13:06:20Z", - "type":"started"}, - {"timestamp":"2018-04-12T13:06:20Z", - "type":"added"}], - "node":"dbcore@db2.bigblue.cloudant.net", - "start_time":"2018-04-12T13:06:20Z"}]} + m_response_ok = mock.MagicMock() + type(m_response_ok).status_code = mock.PropertyMock(return_value=200) + type(m_response_ok).text = mock.PropertyMock(return_value='{"total_rows":1,"offset":0,\ + "jobs":[{"database":null,\ + "id":"f11105eaaded4981d21ff8ebf846f48b+create_target",\ + "pid":"<0.5866.6800>",\ + "source":"https://clientlibs-test:*****@clientlibs-test.cloudant.com/largedb1g/",\ + "target":"https://tomblench:*****@tomblench.cloudant.com/largedb1g/",\ + "user":"tomblench",\ + "doc_id":null,\ + "history":[{"timestamp":"2018-04-12T13:06:20Z",\ + "type":"started"},\ + {"timestamp":"2018-04-12T13:06:20Z",\ + "type":"added"}],\ + "node":"dbcore@db2.bigblue.cloudant.net",\ + "start_time":"2018-04-12T13:06:20Z"}]}') self.client.r_session.get = mock.Mock(return_value=m_response_ok) scheduler = Scheduler(self.client) response = scheduler.list_jobs(skip=0, limit=10)