Skip to content
This repository has been archived by the owner on Mar 11, 2022. It is now read-only.

Commit

Permalink
Stopped raising exception on conflict retry success
Browse files Browse the repository at this point in the history
Moved exception raise into else block.
Added tests for conflict retry cases:
i) max_tries exhuastion
ii) success on retry
  • Loading branch information
ricellis committed Jul 3, 2018
1 parent f0d0010 commit fa1cf06
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased

- [NEW] Add new view parameters, `stable` and `update`, as keyword arguments to `get_view_result`.
- [FIXED] Case where an exception was raised after successful retry when using `doc.update_field`.

# 2.9.0 (2018-06-13)

Expand Down
3 changes: 2 additions & 1 deletion src/cloudant/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ def _update_field(self, action, field, value, max_tries, tries=0):
if tries < max_tries and ex.response.status_code == 409:
self._update_field(
action, field, value, max_tries, tries=tries+1)
raise
else:
raise

def update_field(self, action, field, value, max_tries=10):
"""
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/document_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,62 @@ def test_update_field(self):
self.assertTrue(doc['_rev'].startswith('2-'))
self.assertEqual(doc['pets'], ['cat', 'dog', 'fish'])

@mock.patch('cloudant.document.Document.save')
def test_update_field_maxretries(self, m_save):
"""
Test that conflict retries work for updating a single field.
"""
# Create a doc
doc = Document(self.db, 'julia006')
doc['name'] = 'julia'
doc['age'] = 6
doc.create()
self.assertTrue(doc['_rev'].startswith('1-'))
self.assertEqual(doc['age'], 6)
# Mock conflicts when saving updates
m_save.side_effect = requests.HTTPError(response=mock.Mock(status_code=409, reason='conflict'))
# Tests that failing on retry eventually throws
with self.assertRaises(requests.HTTPError) as cm:
doc.update_field(doc.field_set, 'age', 7, max_tries=2)

# There is an off-by-one error for "max_tries"
# It really means max_retries i.e. 1 attempt
# followed by a max of 2 retries
self.assertEqual(m_save.call_count, 3)
self.assertEqual(cm.exception.response.status_code, 409)
self.assertEqual(cm.exception.response.reason, 'conflict')
# Fetch again before asserting, otherwise we assert against
# the locally updated age field
doc.fetch()
self.assertFalse(doc['_rev'].startswith('2-'))
self.assertNotEqual(doc['age'], 7)

def test_update_field_success_on_retry(self):
"""
Test that conflict retries work for updating a single field.
"""
# Create a doc
doc = Document(self.db, 'julia006')
doc['name'] = 'julia'
doc['age'] = 6
doc.create()
self.assertTrue(doc['_rev'].startswith('1-'))
self.assertEqual(doc['age'], 6)

# Mock when saving the document
# 1st call throw a 409
# 2nd call delegate to the real doc.save()
with mock.patch('cloudant.document.Document.save',
side_effect=[requests.HTTPError(response=mock.Mock(status_code=409, reason='conflict')),
doc.save()]) as m_save:
# A list of side effects containing only 1 element
doc.update_field(doc.field_set, 'age', 7, max_tries=1)
# Two calls to save, one with a 409 and one that succeeds
self.assertEqual(m_save.call_count, 2)
# Check that the _rev and age field were updated
self.assertTrue(doc['_rev'].startswith('2-'))
self.assertEqual(doc['age'], 7)

def test_delete_document_failure(self):
"""
Test failure condition when attempting to remove a document
Expand Down

0 comments on commit fa1cf06

Please sign in to comment.