class NotModifiedTest(BaseTest): def setUp(self): super(NotModifiedTest, self).setUp() self.stored = self.collection.create_record({}) self.resource = BaseResource(request=self.get_request(), context=self.get_context()) self.resource.collection_get() current = self.last_response.headers['ETag'] self.resource.request.headers['If-None-Match'] = current def test_collection_returns_200_if_change_meanwhile(self): self.resource.request.headers['If-None-Match'] = '"42"' self.resource.collection_get() # Not raising. def test_collection_returns_304_if_no_change_meanwhile(self): try: self.resource.collection_get() except httpexceptions.HTTPNotModified as e: error = e self.assertEqual(error.code, 304) self.assertIsNotNone(error.headers.get('ETag')) self.assertIsNotNone(error.headers.get('Last-Modified')) def test_single_record_returns_304_if_no_change_meanwhile(self): self.resource.record_id = self.stored['id'] try: self.resource.get() except httpexceptions.HTTPNotModified as e: error = e self.assertEqual(error.code, 304) self.assertIsNotNone(error.headers.get('ETag')) self.assertIsNotNone(error.headers.get('Last-Modified')) def test_single_record_last_modified_is_returned(self): self.resource.timestamp = 0 self.resource.record_id = self.stored['id'] try: self.resource.get() except httpexceptions.HTTPNotModified as e: error = e self.assertNotIn('1970', error.headers['Last-Modified']) def test_if_none_match_empty_raises_invalid(self): self.resource.request.headers['If-None-Match'] = '""' self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.collection_get) def test_if_none_match_without_quotes_raises_invalid(self): self.resource.request.headers['If-None-Match'] = '1234' self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.collection_get) def test_if_none_match_not_integer_raises_invalid(self): self.resource.request.headers['If-None-Match'] = '"ab"' self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.collection_get)
def test_collection_timestamp_is_not_updated_if_no_field_changed(self): self.resource.request.json = {'data': {'some': 'change'}} self.resource.patch() self.resource = BaseResource(self.get_request()) self.resource.collection_get()['data'] last_modified = int(self.last_response.headers['ETag'][1:-1]) self.assertEquals(self.result['last_modified'], last_modified)
def test_the_timestamp_header_is_equal_to_last_modification(self): result = self.resource.collection_post()['data'] modification = result['last_modified'] self.resource = BaseResource(self.get_request()) self.resource.collection_get() header = int(self.last_response.headers['ETag'][1:-1]) self.assertEqual(header, modification)
def setUp(self): super(NotModifiedTest, self).setUp() self.stored = self.db.create(self.resource, 'bob', {}) self.resource = BaseResource(self.get_request()) self.resource.collection_get() current = self.last_response.headers['Last-Modified'] self.resource.request.headers['If-Modified-Since'] = current
def setUp(self): super(NotModifiedTest, self).setUp() self.stored = self.collection.create_record({}) self.resource = BaseResource(request=self.get_request(), context=self.get_context()) self.resource.collection_get() current = self.last_response.headers["ETag"] self.resource.request.headers["If-None-Match"] = current
class NotModifiedTest(BaseTest): def setUp(self): super(NotModifiedTest, self).setUp() self.stored = self.db.create(self.resource, 'bob', {}) self.resource = BaseResource(self.get_request()) self.resource.collection_get() current = self.last_response.headers['Last-Modified'] self.resource.request.headers['If-Modified-Since'] = current def test_collection_returns_304_if_no_change_meanwhile(self): try: self.resource.collection_get() except httpexceptions.HTTPNotModified as e: error = e self.assertEqual(error.code, 304) self.assertIsNotNone(error.headers.get('Last-Modified')) def test_single_record_returns_304_if_no_change_meanwhile(self): self.resource.record_id = self.stored['id'] try: self.resource.get() except httpexceptions.HTTPNotModified as e: error = e self.assertEqual(error.code, 304) self.assertIsNotNone(error.headers.get('Last-Modified'))
class PatchTest(BaseTest): def setUp(self): super(PatchTest, self).setUp() self.stored = self.collection.create_record({}) self.resource.record_id = self.stored['id'] self.resource.request.json = {'data': {'some': 'change'}} self.resource.mapping.typ.unknown = 'preserve' self.result = self.resource.patch()['data'] def test_modify_record_updates_timestamp(self): before = self.stored['last_modified'] after = self.result['last_modified'] self.assertNotEquals(after, before) def test_patch_record_returns_updated_fields(self): self.assertEquals(self.stored['id'], self.result['id']) self.assertEquals(self.result['some'], 'change') def test_record_timestamp_is_not_updated_if_none_for_missing_field(self): self.resource.request.json = {'data': {'plop': None}} result = self.resource.patch()['data'] self.assertEquals(self.result['last_modified'], result['last_modified']) def test_record_timestamp_is_not_updated_if_no_field_changed(self): self.resource.request.json = {'data': {'some': 'change'}} result = self.resource.patch()['data'] self.assertEquals(self.result['last_modified'], result['last_modified']) def test_collection_timestamp_is_not_updated_if_no_field_changed(self): self.resource.request.json = {'data': {'some': 'change'}} self.resource.patch() self.resource = BaseResource(request=self.get_request(), context=self.get_context()) self.resource.collection_get()['data'] last_modified = int(self.last_response.headers['ETag'][1:-1]) self.assertEquals(self.result['last_modified'], last_modified) def test_timestamp_is_not_updated_if_no_change_after_preprocessed(self): with mock.patch.object(self.resource, 'process_record') as mocked: mocked.return_value = self.result self.resource.request.json = {'data': {'some': 'plop'}} result = self.resource.patch()['data'] self.assertEquals(self.result['last_modified'], result['last_modified']) def test_returns_changed_fields_among_provided_if_behaviour_is_diff(self): self.resource.request.json = {'data': {'unread': True, 'position': 10}} self.resource.request.headers['Response-Behavior'] = 'diff' with mock.patch.object(self.resource.collection, 'update_record', return_value={'unread': True, 'position': 0}): result = self.resource.patch()['data'] self.assertDictEqual(result, {'position': 0}) def test_returns_changed_fields_if_behaviour_is_light(self): self.resource.request.json = {'data': {'unread': True, 'position': 10}} self.resource.request.headers['Response-Behavior'] = 'light' with mock.patch.object(self.resource.collection, 'update_record', return_value={'unread': True, 'position': 0}): result = self.resource.patch()['data'] self.assertDictEqual(result, {'unread': True, 'position': 0})
class PatchTest(BaseTest): def setUp(self): super(PatchTest, self).setUp() self.stored = self.collection.create_record({}) self.resource.record_id = self.stored['id'] self.resource.request.json = {'data': {'position': 10}} schema = ResourceSchema() schema.add(colander.SchemaNode(colander.Boolean(), name='unread', missing=colander.drop)) schema.add(colander.SchemaNode(colander.Int(), name='position', missing=colander.drop)) self.resource.mapping = schema self.result = self.resource.patch()['data'] def test_etag_is_provided(self): self.assertIn('ETag', self.last_response.headers) def test_etag_contains_record_new_timestamp(self): expected = ('"%s"' % self.result['last_modified']) self.assertEqual(expected, self.last_response.headers['ETag']) def test_etag_contains_old_timestamp_if_no_field_changed(self): self.resource.request.json = {'data': {'position': 10}} self.resource.patch()['data'] expected = ('"%s"' % self.result['last_modified']) self.assertEqual(expected, self.last_response.headers['ETag']) def test_modify_record_updates_timestamp(self): before = self.stored['last_modified'] after = self.result['last_modified'] self.assertNotEquals(after, before) def test_patch_record_returns_updated_fields(self): self.assertEquals(self.stored['id'], self.result['id']) self.assertEquals(self.result['position'], 10) def test_record_timestamp_is_not_updated_if_none_for_missing_field(self): self.resource.request.json = {'data': {'polo': None}} result = self.resource.patch()['data'] self.assertEquals(self.result['last_modified'], result['last_modified']) def test_record_timestamp_is_not_updated_if_no_field_changed(self): self.resource.request.json = {'data': {'position': 10}} result = self.resource.patch()['data'] self.assertEquals(self.result['last_modified'], result['last_modified']) def test_collection_timestamp_is_not_updated_if_no_field_changed(self): self.resource.request.json = {'data': {'position': 10}} self.resource.patch() self.resource = BaseResource(request=self.get_request(), context=self.get_context()) self.resource.collection_get()['data'] last_modified = int(self.last_response.headers['ETag'][1:-1]) self.assertEquals(self.result['last_modified'], last_modified) def test_timestamp_is_not_updated_if_no_change_after_preprocessed(self): with mock.patch.object(self.resource, 'process_record') as mocked: mocked.return_value = self.result self.resource.request.json = {'data': {'position': 20}} result = self.resource.patch()['data'] self.assertEquals(self.result['last_modified'], result['last_modified']) def test_returns_changed_fields_among_provided_if_behaviour_is_diff(self): self.resource.request.json = {'data': {'unread': True, 'position': 15}} self.resource.request.headers['Response-Behavior'] = 'diff' with mock.patch.object(self.resource.collection, 'update_record', return_value={'unread': True, 'position': 0}): result = self.resource.patch()['data'] self.assertDictEqual(result, {'position': 0}) def test_returns_changed_fields_if_behaviour_is_light(self): self.resource.request.json = {'data': {'unread': True, 'position': 15}} self.resource.request.headers['Response-Behavior'] = 'light' with mock.patch.object(self.resource.collection, 'update_record', return_value={'unread': True, 'position': 0}): result = self.resource.patch()['data'] self.assertDictEqual(result, {'unread': True, 'position': 0})
class SinceModifiedTest(ThreadMixin, BaseTest): def setUp(self): super(SinceModifiedTest, self).setUp() with mock.patch.object(self.db, '_bump_timestamp') as msec_mocked: for i in range(6): msec_mocked.return_value = i self.resource.collection_post() self.resource.request.validated = {} # reset next def test_filter_with_since_is_exclusive(self): self.resource.request.GET = {'_since': '3'} result = self.resource.collection_get() self.assertEqual(len(result['items']), 2) def test_filter_with_to_is_exclusive(self): self.resource.request.GET = {'_to': '3'} result = self.resource.collection_get() self.assertEqual(len(result['items']), 3) def test_the_timestamp_header_is_equal_to_last_modification(self): result = self.resource.collection_post() modification = result['last_modified'] self.resource = BaseResource(self.get_request()) self.resource.collection_get() header = int(self.last_response.headers['Last-Modified']) self.assertEqual(header, modification) def test_filter_with_since_accepts_numeric_value(self): self.resource.request.GET = {'_since': '6'} self.resource.collection_post() result = self.resource.collection_get() self.assertEqual(len(result['items']), 1) def test_filter_with_since_rejects_non_numeric_value(self): self.resource.request.GET = {'_since': 'abc'} self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.collection_get) def test_filter_with_since_rejects_decimal_value(self): self.resource.request.GET = {'_since': '1.2'} self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.collection_get) def test_filter_from_last_modified_is_exclusive(self): result = self.resource.collection_post() current = result['last_modified'] self.resource.request.GET = {'_since': six.text_type(current)} result = self.resource.collection_get() self.assertEqual(len(result['items']), 0) def test_filter_with_last_modified_includes_deleted_items(self): self.resource.collection_post() result = self.resource.collection_post() current = result['last_modified'] self.resource.record_id = result['id'] self.resource.delete() self.resource.request.GET = {'_since': six.text_type(current)} result = self.resource.collection_get() self.assertEqual(len(result['items']), 1) self.assertTrue(result['items'][0]['deleted']) def test_filter_from_last_header_value_is_exclusive(self): result = self.resource.collection_get() current = int(self.last_response.headers['Last-Modified']) self.resource.request.GET = {'_since': six.text_type(current)} result = self.resource.collection_get() self.assertEqual(len(result['items']), 0) def test_filter_works_with_empty_list(self): self.resource.db_kwargs['user_id'] = 'alice' self.resource.request.GET = {'_since': '3'} result = self.resource.collection_get() self.assertEqual(len(result['items']), 0) def test_timestamp_are_always_identical_on_read(self): def read_timestamp(): self.resource.collection_get() return int(self.last_response.headers['Last-Modified']) before = read_timestamp() now = read_timestamp() after = read_timestamp() self.assertEqual(before, now) self.assertEqual(now, after) def test_timestamp_are_always_incremented_on_creation(self): def read_timestamp(): record = self.resource.collection_post() return record['last_modified'] before = read_timestamp() now = read_timestamp() after = read_timestamp() self.assertTrue(before < now < after) def test_records_created_during_fetch_are_above_fetch_timestamp(self): timestamps = {} def long_fetch(): """Simulate a overhead while reading on storage.""" def delayed_get(*args, **kwargs): time.sleep(.100) # 100 msec return [], 0 with mock.patch.object(self.db, 'get_all', delayed_get): self.resource.collection_get() fetch_at = self.last_response.headers['Last-Modified'] timestamps['fetch'] = int(fetch_at) # Create a real record with no patched timestamp self.resource.collection_post() # Some client start fetching thread = self._create_thread(target=long_fetch) thread.start() # Create record while other is fetching time.sleep(.020) # 20 msec record = self.resource.collection_post() timestamps['post'] = record['last_modified'] # Wait for the fetch to finish thread.join() # Make sure fetch timestamp is below (for next fetch) self.assertTrue(timestamps['post'] > timestamps['fetch'])
class SinceModifiedTest(ThreadMixin, BaseTest): def setUp(self): super(SinceModifiedTest, self).setUp() self.resource.request.validated = {'data': {}} with mock.patch.object(self.collection.storage, '_bump_timestamp') as msec_mocked: for i in range(6): msec_mocked.return_value = i self.resource.collection_post() def test_filter_with_since_is_exclusive(self): self.resource.request.GET = {'_since': '3'} result = self.resource.collection_get() self.assertEqual(len(result['data']), 2) def test_filter_with__to_is_exclusive(self): self.resource.request.GET = {'_to': '3'} result = self.resource.collection_get() self.assertEqual(len(result['data']), 3) def test_filter_with__before_is_exclusive(self): self.resource.request.GET = {'_before': '3'} result = self.resource.collection_get() self.assertEqual(len(result['data']), 3) def test_filter_with__to_return_an_alert_header(self): self.resource.request.GET = {'_to': '3'} self.resource.collection_get() self.assertIn('Alert', self.resource.request.response.headers) alert = self.resource.request.response.headers['Alert'] self.assertDictEqual( decode_header(json.loads(alert)), { 'code': 'soft-eol', 'message': ('_to is now deprecated, ' 'you should use _before instead'), 'url': ('http://cliquet.rtfd.org/en/2.4.0/api/resource' '.html#list-of-available-url-parameters') }) def test_the_timestamp_header_is_equal_to_last_modification(self): result = self.resource.collection_post()['data'] modification = result['last_modified'] self.resource = BaseResource(request=self.get_request(), context=self.get_context()) self.resource.collection_get() header = int(self.last_response.headers['ETag'][1:-1]) self.assertEqual(header, modification) def test_filter_with_since_accepts_numeric_value(self): self.resource.request.GET = {'_since': '6'} self.resource.collection_post() result = self.resource.collection_get() self.assertEqual(len(result['data']), 1) def test_filter_with_since_rejects_non_numeric_value(self): self.resource.request.GET = {'_since': 'abc'} self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.collection_get) def test_filter_with_since_rejects_decimal_value(self): self.resource.request.GET = {'_since': '1.2'} self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.collection_get) def test_filter_from_last_modified_is_exclusive(self): result = self.resource.collection_post()['data'] current = result['last_modified'] self.resource.request.GET = {'_since': six.text_type(current)} result = self.resource.collection_get() self.assertEqual(len(result['data']), 0) def test_filter_with_last_modified_includes_deleted_data(self): self.resource.collection_post() result = self.resource.collection_post()['data'] current = result['last_modified'] self.resource.record_id = result['id'] self.resource.delete() self.resource.request.GET = {'_since': six.text_type(current)} result = self.resource.collection_get() self.assertEqual(len(result['data']), 1) self.assertTrue(result['data'][0]['deleted']) def test_filter_from_last_header_value_is_exclusive(self): self.resource.collection_get() current = int(self.last_response.headers['ETag'][1:-1]) self.resource.request.GET = {'_since': six.text_type(current)} result = self.resource.collection_get() self.assertEqual(len(result['data']), 0) def test_filter_works_with_empty_list(self): self.resource.collection.parent_id = 'alice' self.resource.request.GET = {'_since': '3'} result = self.resource.collection_get() self.assertEqual(len(result['data']), 0) def test_timestamp_are_always_identical_on_read(self): def read_timestamp(): self.resource.collection_get() return int(self.last_response.headers['ETag'][1:-1]) before = read_timestamp() now = read_timestamp() after = read_timestamp() self.assertEqual(before, now) self.assertEqual(now, after) def test_timestamp_are_always_incremented_on_creation(self): def read_timestamp(): record = self.resource.collection_post()['data'] return record['last_modified'] before = read_timestamp() now = read_timestamp() after = read_timestamp() self.assertTrue(before < now < after) def test_records_created_during_fetch_are_above_fetch_timestamp(self): timestamps = {} def long_fetch(): """Simulate a overhead while reading on storage.""" def delayed_get(*args, **kwargs): time.sleep(.100) # 100 msec return [], 0 with mock.patch.object(self.collection.storage, 'get_all', delayed_get): self.resource.collection_get() fetch_at = self.last_response.headers['ETag'][1:-1] timestamps['fetch'] = int(fetch_at) # Create a real record with no patched timestamp self.resource.collection_post() # Some client start fetching thread = self._create_thread(target=long_fetch) thread.start() # Create record while other is fetching time.sleep(.020) # 20 msec record = self.resource.collection_post()['data'] timestamps['post'] = record['last_modified'] # Wait for the fetch to finish thread.join() # Make sure fetch timestamp is below (for next fetch) self.assertTrue(timestamps['post'] > timestamps['fetch'])