def test_parseMemo__json_error(self): # parseMemo() returns None for formally invalid JSON strings. resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) self.assertIs(None, range_factory.parseMemo('foo')) self.assertEqual( ['memo is not a valid JSON string.'], self.error_messages)
def test_parseMemo__json_no_sequence(self): # parseMemo() accepts only JSON representations of lists. resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) self.assertIs(None, range_factory.parseMemo(simplejson.dumps(1))) self.assertEqual(['memo must be the JSON representation of a list.'], self.error_messages)
def test_parseMemo__descending_sort_order(self): # Validation of a memo string against a descending sort order works. resultset = self.makeStormResultSet() resultset.order_by(Desc(Person.id)) range_factory = StormRangeFactory(resultset, self.logError) self.assertEqual( [1], range_factory.parseMemo(simplejson.dumps([1])))
def test_getOrderValuesFor__decorated_result_set(self): # getOrderValuesFor() knows how to retrieve SQL sort values # from DecoratedResultSets. resultset = self.makeDecoratedStormResultSet() range_factory = StormRangeFactory(resultset) self.assertEqual( [resultset[0].id], range_factory.getOrderValuesFor(resultset[0]))
def test_getSlice__returns_ShadowedList(self): # getSlice() returns lists. resultset = self.makeStormResultSet() resultset.order_by(Person.id) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3) self.assertIsInstance(sliced_result, ShadowedList)
def test_limitsGroupedByOrderDirection(self): # limitsGroupedByOrderDirection() returns a sequence of # (expressions, memos), where expressions is a list of # ORDER BY expressions which either are all instances of # PropertyColumn, or are all instances of Desc(PropertyColumn). # memos are the related limit values. resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) order_by = [ Person.id, Person.datecreated, Person.name, Person.display_name ] limits = [1, datetime(2011, 7, 25, 0, 0, 0), 'foo', 'bar'] result = range_factory.limitsGroupedByOrderDirection(order_by, limits) self.assertEqual([(order_by, limits)], result) order_by = [ Desc(Person.id), Desc(Person.datecreated), Desc(Person.name), Desc(Person.display_name) ] result = range_factory.limitsGroupedByOrderDirection(order_by, limits) self.assertEqual([(order_by, limits)], result) order_by = [ Person.id, Person.datecreated, Desc(Person.name), Desc(Person.display_name) ] result = range_factory.limitsGroupedByOrderDirection(order_by, limits) self.assertEqual([(order_by[:2], limits[:2]), (order_by[2:], limits[2:])], result)
def test_getSlice__forward_without_memo(self): resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) all_results = list(resultset) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3) self.assertEqual(all_results[:3], list(sliced_result))
def test_getOrderValuesFor__two_sort_columns(self): # Sorting by more than one column is supported. resultset = self.makeStormResultSet() resultset.order_by(Person.displayname, Person.name) range_factory = StormRangeFactory(resultset) order_values = range_factory.getOrderValuesFor(resultset[0]) self.assertEqual( [resultset[0].displayname, resultset[0].name], order_values)
def test_getOrderValuesFor__descending_sort_order(self): # getOrderValuesFor() can retrieve values from reverse sorted # columns. resultset = self.makeStormResultSet() resultset = resultset.order_by(Desc(Person.id)) range_factory = StormRangeFactory(resultset) self.assertEqual( [resultset[0].id], range_factory.getOrderValuesFor(resultset[0]))
def test_whereExpressions__asc(self): """For ascending sort order, whereExpressions() returns the WHERE clause expression > memo. """ resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) [where_clause] = range_factory.whereExpressions([Person.id], [1]) self.assertEquals('(Person.id) > (1)', compile(where_clause))
def test_getSlice__forward_with_memo(self): resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) all_results = list(resultset) memo = simplejson.dumps([all_results[0].name, all_results[0].id]) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, memo) self.assertEqual(all_results[1:4], list(sliced_result))
def test_parseMemo__json_no_sequence(self): # parseMemo() accepts only JSON representations of lists. resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) self.assertIs(None, range_factory.parseMemo(simplejson.dumps(1))) self.assertEqual( ['memo must be the JSON representation of a list.'], self.error_messages)
def test_getOrderValuesFor__one_sort_column(self): # StormRangeFactory.getOrderValuesFor() returns the values # of the fields used in order_by expresssions for a given # result row. resultset = self.makeStormResultSet() resultset.order_by(Person.id) range_factory = StormRangeFactory(resultset) order_values = range_factory.getOrderValuesFor(resultset[0]) self.assertEqual([resultset[0].id], order_values)
def test_getSlice__backward_without_memo(self): resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) all_results = list(resultset) expected = all_results[-3:] expected.reverse() range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, forwards=False) self.assertEqual(expected, list(sliced_result))
def test_getOrderValuesFor__value_from_second_element_of_result_row(self): # getOrderValuesFor() can retrieve values from attributes # of any Storm table class instance which appear in a result row. resultset = self.makeDecoratedStormResultSet() resultset = resultset.order_by(LibraryFileAlias.id) plain_resultset = resultset.get_plain_result_set() range_factory = StormRangeFactory(resultset) self.assertEqual([plain_resultset[0][1].id], range_factory.getOrderValuesFor(plain_resultset[0]))
def test_parseMemo__invalid_iso_timestamp_value(self): # An ISO timestamp with an invalid date is rejected as a memo # string. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated) range_factory = StormRangeFactory(resultset, self.logError) invalid_timestamp_json = '["2011-05-35T11:30:30"]' self.assertIs(None, range_factory.parseMemo(invalid_timestamp_json)) self.assertEqual(["Invalid datetime value: '2011-05-35T11:30:30'"], self.error_messages)
def test_getSlice__decorated_resultset(self): resultset = self.makeDecoratedStormResultSet() resultset.order_by(LibraryFileAlias.id) all_results = list(resultset) plain_results = list(resultset.get_plain_result_set()) memo = simplejson.dumps([resultset.get_plain_result_set()[0][1].id]) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, memo) self.assertEqual(all_results[1:4], list(sliced_result)) self.assertEqual(plain_results[1:4], sliced_result.shadow_values)
def test_getOrderValuesFor__value_from_second_element_of_result_row(self): # getOrderValuesFor() can retrieve values from attributes # of any Storm table class instance which appear in a result row. resultset = self.makeDecoratedStormResultSet() resultset = resultset.order_by(LibraryFileAlias.id) plain_resultset = resultset.get_plain_result_set() range_factory = StormRangeFactory(resultset) self.assertEqual( [plain_resultset[0][1].id], range_factory.getOrderValuesFor(plain_resultset[0]))
def test_parseMemo__valid_data(self): # If a memo string contains valid data, parseMemo returns this data. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated, Person.name, Person.id) range_factory = StormRangeFactory(resultset, self.logError) valid_memo = [ datetime(2011, 7, 25, 11, 30, 30, 45, tzinfo=pytz.UTC), 'foo', 1] json_data = simplejson.dumps(valid_memo, cls=DateTimeJSONEncoder) self.assertEqual(valid_memo, range_factory.parseMemo(json_data)) self.assertEqual(0, len(self.error_messages))
def test_parseMemo__memo_type_check(self): # parseMemo() accepts only lists containing values that can # be used in sort expression of the given result set. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated, Person.name, Person.id) range_factory = StormRangeFactory(resultset, self.logError) invalid_memo = [datetime(2011, 7, 25, 11, 30, 30, 45), 'foo', 'bar'] json_data = simplejson.dumps(invalid_memo, cls=DateTimeJSONEncoder) self.assertIs(None, range_factory.parseMemo(json_data)) self.assertEqual(["Invalid parameter: 'bar'"], self.error_messages)
def test_getSlice_backward_with_memo(self): resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) all_results = list(resultset) expected = all_results[1:4] expected.reverse() memo = simplejson.dumps([all_results[4].name, all_results[4].id]) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, memo, forwards=False) self.assertEqual(expected, list(sliced_result))
def test_parseMemo__wrong_list_length(self): # parseMemo() accepts only lists which have as many elements # as the number of sort expressions used in the SQL query of # the result set. resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) range_factory = StormRangeFactory(resultset, self.logError) self.assertIs(None, range_factory.parseMemo(simplejson.dumps([1]))) expected_message = ( 'Invalid number of elements in memo string. Expected: 2, got: 1') self.assertEqual([expected_message], self.error_messages)
def test_parseMemo__short_iso_timestamp_with_tzoffset(self): # An ISO timestamp with fractions of a second and a time zone # offset (YYYY-MM-DDThh:mm:ss.ffffff+hh:mm) is a valid value # for columns which store datetime values. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated) range_factory = StormRangeFactory(resultset, self.logError) valid_long_timestamp_json = '["2011-07-25T11:30:30-01:00"]' self.assertEqual([datetime(2011, 7, 25, 12, 30, 30, tzinfo=pytz.UTC)], range_factory.parseMemo(valid_long_timestamp_json)) self.assertEqual(0, len(self.error_messages))
def test_parseMemo__nonsensical_iso_timestamp_value(self): # A memo string is rejected when an ISO timespamp is expected # but a nonsensical string is provided. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated) range_factory = StormRangeFactory(resultset, self.logError) nonsensical_timestamp_json = '["bar"]' self.assertIs(None, range_factory.parseMemo(nonsensical_timestamp_json)) self.assertEqual(["Invalid datetime value: 'bar'"], self.error_messages)
def test_reverseSortOrder(self): # reverseSortOrder() wraps a plain PropertyColumn instance into # Desc(), and it returns the plain PropertyCOlumn for a Desc() # expression. resultset = self.makeStormResultSet() resultset.order_by(Person.id, Desc(Person.name)) range_factory = StormRangeFactory(resultset, self.logError) reverse_person_id, person_name = range_factory.reverseSortOrder() self.assertTrue(isinstance(reverse_person_id, Desc)) self.assertIs(Person.id, reverse_person_id.expr) self.assertIs(Person.name, person_name)
def test_lessThanOrGreaterThanExpression__desc(self): # beforeOrAfterExpression() returns an expression # (col1, col2,..) < (memo1, memo2...) for descending sort order. resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) expressions = [Desc(Person.id), Desc(Person.name)] limits = [1, 'foo'] limit_expression = range_factory.lessThanOrGreaterThanExpression( expressions, limits) self.assertEqual("(Person.id, Person.name) < (1, E'foo')", compile(limit_expression))
def test_parseMemo__wrong_list_length(self): # parseMemo() accepts only lists which have as many elements # as the number of sort expressions used in the SQL query of # the result set. resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) range_factory = StormRangeFactory(resultset, self.logError) self.assertIs( None, range_factory.parseMemo(simplejson.dumps([1]))) expected_message = ( 'Invalid number of elements in memo string. Expected: 2, got: 1') self.assertEqual([expected_message], self.error_messages)
def test_lessThanOrGreaterThanExpression__desc(self): # beforeOrAfterExpression() returns an expression # (col1, col2,..) < (memo1, memo2...) for descending sort order. resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) expressions = [Desc(Person.id), Desc(Person.name)] limits = [1, 'foo'] limit_expression = range_factory.lessThanOrGreaterThanExpression( expressions, limits) self.assertEqual( "(Person.id, Person.name) < (1, 'foo')", compile(limit_expression))
def test_parseMemo__nonsensical_iso_timestamp_value(self): # A memo string is rejected when an ISO timespamp is expected # but a nonsensical string is provided. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated) range_factory = StormRangeFactory(resultset, self.logError) nonsensical_timestamp_json = '["bar"]' self.assertIs( None, range_factory.parseMemo(nonsensical_timestamp_json)) self.assertEqual( ["Invalid datetime value: 'bar'"], self.error_messages)
def test_parseMemo__long_iso_timestamp_with_tzoffset(self): # An ISO timestamp with fractions of a second and a time zone # offset (YYYY-MM-DDThh:mm:ss.ffffff+hh:mm) is a valid value # for columns which store datetime values. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated) range_factory = StormRangeFactory(resultset, self.logError) valid_long_timestamp_json = '["2011-07-25T11:30:30.123456+01:00"]' self.assertEqual( [datetime(2011, 7, 25, 10, 30, 30, 123456, tzinfo=pytz.UTC)], range_factory.parseMemo(valid_long_timestamp_json)) self.assertEqual(0, len(self.error_messages))
def test_parseMemo__short_iso_timestamp(self): # An ISO timestamp without fractions of a second # (YYYY-MM-DDThh:mm:ss) is a valid value for columns which # store datetime values. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated) range_factory = StormRangeFactory(resultset, self.logError) valid_short_timestamp_json = '["2011-07-25T11:30:30"]' self.assertEqual( [datetime(2011, 7, 25, 11, 30, 30, tzinfo=pytz.UTC)], range_factory.parseMemo(valid_short_timestamp_json)) self.assertEqual(0, len(self.error_messages))
def test_parseMemo__invalid_iso_timestamp_value(self): # An ISO timestamp with an invalid date is rejected as a memo # string. resultset = self.makeStormResultSet() resultset.order_by(Person.datecreated) range_factory = StormRangeFactory(resultset, self.logError) invalid_timestamp_json = '["2011-05-35T11:30:30"]' self.assertIs( None, range_factory.parseMemo(invalid_timestamp_json)) self.assertEqual( ["Invalid datetime value: '2011-05-35T11:30:30'"], self.error_messages)
def test_getSliceByIndex__storm_result_set(self): # StormRangeFactory.getSliceByIndex() returns a slice of the # resultset, wrapped into a ShadowedList. For plain Storm # result sets, the main values and the shadow values are both # the corresponding elements of the result set. resultset = self.makeStormResultSet() all_results = list(resultset) range_factory = StormRangeFactory(resultset) sliced = range_factory.getSliceByIndex(2, 4) self.assertIsInstance(sliced, ShadowedList) self.assertEqual(all_results[2:4], list(sliced)) self.assertEqual(all_results[2:4], sliced.shadow_values)
def test_getSlice__backwards_then_forwards(self): # A slice can be retrieved in both directions from one factory. resultset = self.makeStormResultSet() resultset.order_by(Person.id) all_results = list(resultset) memo = simplejson.dumps([all_results[2].id]) range_factory = StormRangeFactory(resultset) backward_slice = range_factory.getSlice( size=2, endpoint_memo=memo, forwards=False) backward_slice.reverse() self.assertEqual(all_results[:2], list(backward_slice)) forward_slice = range_factory.getSlice( size=2, endpoint_memo=memo, forwards=True) self.assertEqual(all_results[3:5], list(forward_slice))
def test_whereExpressions__two_sort_columns_desc_desc(self): """If the descending sort columns c1, c2 and the memo values m1, m2 are specified, whereExpressions() returns a WHERE expressions comparing the tuple (c1, c2) with the memo tuple (m1, m2): (c1, c2) < (m1, m2) """ resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) [where_clause] = range_factory.whereExpressions( [Desc(Person.id), Desc(Person.name)], [1, 'foo']) self.assertEquals( "(Person.id, Person.name) < (1, 'foo')", compile(where_clause))
def test_equalsExpressionsFromLimits(self): resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) order_by = [ Person.id, Person.datecreated, Desc(Person.name), Desc(Person.displayname)] limits = [ 1, datetime(2011, 07, 25, 0, 0, 0, tzinfo=pytz.UTC), 'foo', 'bar'] limits = range_factory.limitsGroupedByOrderDirection(order_by, limits) equals_expressions = range_factory.equalsExpressionsFromLimits(limits) equals_expressions = map(compile, equals_expressions) self.assertEqual( ['Person.id = ?', 'Person.datecreated = ?', 'Person.name = ?', 'Person.displayname = ?'], equals_expressions)
def _getBatchNavigator(self, grantees): """Return the batch navigator to be used to batch the grantees.""" return BatchNavigator(grantees, self.request, hide_counts=True, size=config.launchpad.default_batch_size, range_factory=StormRangeFactory(grantees))
def test_StormRangeFactory__EmptyResultSet(self): # It is possible to create StormRangeFactory instances for # EmptyResultSets, resultset = EmptyResultSet() range_factory = StormRangeFactory(resultset) self.assertEqual(0, range_factory.rough_length) self.assertEmptyResultSetsWorking(range_factory)
def test_getEndpointMemos(self): # getEndpointMemos() returns JSON representations of the # sort fields of the first and last element of a batch. resultset = self.makeStormResultSet() resultset.order_by(Person.name) range_factory = StormRangeFactory(resultset) memo_value = range_factory.getOrderValuesFor(resultset[0]) request = LaunchpadTestRequest( QUERY_STRING='memo=%s' % simplejson.dumps(memo_value)) batchnav = BatchNavigator( resultset, request, size=3, range_factory=range_factory) first, last = range_factory.getEndpointMemos(batchnav.batch) expected_first = simplejson.dumps( [resultset[1].name], cls=DateTimeJSONEncoder) expected_last = simplejson.dumps( [resultset[3].name], cls=DateTimeJSONEncoder) self.assertEqual(expected_first, first) self.assertEqual(expected_last, last)
def test_getEndpointMemos__decorated_result_set(self): # getEndpointMemos() works for DecoratedResultSet # instances too. resultset = self.makeDecoratedStormResultSet() resultset.order_by(LibraryFileAlias.id) range_factory = StormRangeFactory(resultset) request = LaunchpadTestRequest() batchnav = BatchNavigator( resultset, request, size=3, range_factory=range_factory) first, last = range_factory.getEndpointMemos(batchnav.batch) expected_first = simplejson.dumps( [resultset.get_plain_result_set()[0][1].id], cls=DateTimeJSONEncoder) expected_last = simplejson.dumps( [resultset.get_plain_result_set()[2][1].id], cls=DateTimeJSONEncoder) self.assertEqual(expected_first, first) self.assertEqual(expected_last, last)