Index: appengine/monorail/framework/test/sorting_test.py |
diff --git a/appengine/monorail/framework/test/sorting_test.py b/appengine/monorail/framework/test/sorting_test.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2fe1176e6179edba4e07338604b54103c7ac4ef1 |
--- /dev/null |
+++ b/appengine/monorail/framework/test/sorting_test.py |
@@ -0,0 +1,220 @@ |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is govered by a BSD-style |
+# license that can be found in the LICENSE file or at |
+# https://developers.google.com/open-source/licenses/bsd |
+ |
+"""Unit tests for sorting.py functions.""" |
+ |
+import unittest |
+ |
+import mox |
+ |
+from framework import sorting |
+from proto import tracker_pb2 |
+from testing import fake |
+from testing import testing_helpers |
+from tracker import tracker_bizobj |
+ |
+ |
+def MakeDescending(accessor): |
+ return sorting._MaybeMakeDescending(accessor, True) |
+ |
+ |
+class SortingTest(unittest.TestCase): |
+ |
+ def setUp(self): |
+ self.mox = mox.Mox() |
+ self.default_cols = 'a b c' |
+ self.builtin_cols = 'a b x y z' |
+ self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789) |
+ self.config.component_defs.append(tracker_bizobj.MakeComponentDef( |
+ 11, 789, 'Database', 'doc', False, [], [], 0, 0)) |
+ self.config.component_defs.append(tracker_bizobj.MakeComponentDef( |
+ 22, 789, 'User Interface', 'doc', True, [], [], 0, 0)) |
+ self.config.component_defs.append(tracker_bizobj.MakeComponentDef( |
+ 33, 789, 'Installer', 'doc', False, [], [], 0, 0)) |
+ |
+ def tearDown(self): |
+ self.mox.UnsetStubs() |
+ self.mox.ResetAll() |
+ |
+ def testMakeSingleSortKeyAccessor_Status(self): |
+ """Sorting by status should create an accessor for that column.""" |
+ self.mox.StubOutWithMock(sorting, '_IndexOrLexical') |
+ status_names = [wks.status for wks in self.config.well_known_statuses] |
+ sorting._IndexOrLexical(status_names, 'status accessor') |
+ self.mox.ReplayAll() |
+ |
+ sorting._MakeSingleSortKeyAccessor( |
+ 'status', self.config, {'status': 'status accessor'}, [], {}, []) |
+ self.mox.VerifyAll() |
+ |
+ def testMakeSingleSortKeyAccessor_Component(self): |
+ """Sorting by component should create an accessor for that column.""" |
+ self.mox.StubOutWithMock(sorting, '_IndexListAccessor') |
+ component_ids = [11, 33, 22] |
+ sorting._IndexListAccessor(component_ids, 'component accessor') |
+ self.mox.ReplayAll() |
+ |
+ sorting._MakeSingleSortKeyAccessor( |
+ 'component', self.config, {'component': 'component accessor'}, [], {}, []) |
+ self.mox.VerifyAll() |
+ |
+ def testMakeSingleSortKeyAccessor_OtherBuiltInColunms(self): |
+ """Sorting a built-in column should create an accessor for that column.""" |
+ accessor = sorting._MakeSingleSortKeyAccessor( |
+ 'buildincol', self.config, {'buildincol': 'accessor'}, [], {}, []) |
+ self.assertEqual('accessor', accessor) |
+ |
+ def testMakeSingleSortKeyAccessor_OtherBuiltInUserColunms(self): |
+ """Sorting a built-in user column should create a user accessor.""" |
+ self.mox.StubOutWithMock(sorting, '_UserEditNameAccessor') |
+ users_by_id = {111L: 'fake user'} |
+ sorting._UserEditNameAccessor(users_by_id, 'owner accessor') |
+ self.mox.ReplayAll() |
+ |
+ sorting._MakeSingleSortKeyAccessor( |
+ 'owner', self.config, {'owner': 'owner accessor'}, |
+ ['owner'], users_by_id, []) |
+ self.mox.VerifyAll() |
+ |
+ def testIndexOrLexical(self): |
+ well_known_values = ['x-a', 'x-b', 'x-c', 'x-d'] |
+ art = 'this is a fake artifact' |
+ |
+ # Case 1: accessor generates no values. |
+ base_accessor = lambda art: None |
+ accessor = sorting._IndexOrLexical(well_known_values, base_accessor) |
+ self.assertEqual(sorting.MAX_STRING, accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual(sorting.DescendingValue(sorting.MAX_STRING), |
+ neg_accessor(art)) |
+ |
+ # Case 2: accessor generates a value, but it is an empty value. |
+ base_accessor = lambda art: '' |
+ accessor = sorting._IndexOrLexical(well_known_values, base_accessor) |
+ self.assertEqual(sorting.MAX_STRING, accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual(sorting.DescendingValue(sorting.MAX_STRING), |
+ neg_accessor(art)) |
+ |
+ # Case 3: A single well-known value |
+ base_accessor = lambda art: 'x-c' |
+ accessor = sorting._IndexOrLexical(well_known_values, base_accessor) |
+ self.assertEqual(2, accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual(-2, neg_accessor(art)) |
+ |
+ # Case 4: A single odd-ball value |
+ base_accessor = lambda art: 'x-zzz' |
+ accessor = sorting._IndexOrLexical(well_known_values, base_accessor) |
+ self.assertEqual('x-zzz', accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual( |
+ sorting.DescendingValue('x-zzz'), neg_accessor(art)) |
+ |
+ def testIndexListAccessor_SomeWellKnownValues(self): |
+ """Values sort according to their position in the well-known list.""" |
+ well_known_values = [11, 33, 22] # These represent component IDs. |
+ art = fake.MakeTestIssue(789, 1, 'sum 1', 'New', 111L) |
+ base_accessor = lambda issue: issue.component_ids |
+ accessor = sorting._IndexListAccessor(well_known_values, base_accessor) |
+ |
+ # Case 1: accessor generates no values. |
+ self.assertEqual(sorting.MAX_STRING, accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual(sorting.MAX_STRING, neg_accessor(art)) |
+ |
+ # Case 2: A single well-known value |
+ art.component_ids = [33] |
+ self.assertEqual([1], accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual([-1], neg_accessor(art)) |
+ |
+ # Case 3: Multiple well-known and odd-ball values |
+ art.component_ids = [33, 11, 99] |
+ self.assertEqual([0, 1, sorting.MAX_STRING], accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual([sorting.MAX_STRING, -1, 0], |
+ neg_accessor(art)) |
+ |
+ def testIndexListAccessor_NoWellKnownValues(self): |
+ """When there are no well-known values, all values sort last.""" |
+ well_known_values = [] # Nothing pre-defined, so everything is oddball |
+ art = fake.MakeTestIssue(789, 1, 'sum 1', 'New', 111L) |
+ base_accessor = lambda issue: issue.component_ids |
+ accessor = sorting._IndexListAccessor(well_known_values, base_accessor) |
+ |
+ # Case 1: accessor generates no values. |
+ self.assertEqual(sorting.MAX_STRING, accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual(sorting.MAX_STRING, neg_accessor(art)) |
+ |
+ # Case 2: A single oddball value |
+ art.component_ids = [33] |
+ self.assertEqual([sorting.MAX_STRING], accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual([sorting.MAX_STRING], neg_accessor(art)) |
+ |
+ # Case 3: Multiple odd-ball values |
+ art.component_ids = [33, 11, 99] |
+ self.assertEqual( |
+ [sorting.MAX_STRING, sorting.MAX_STRING, sorting.MAX_STRING], |
+ accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual( |
+ [sorting.MAX_STRING, sorting.MAX_STRING, sorting.MAX_STRING], |
+ neg_accessor(art)) |
+ |
+ def testIndexOrLexicalList(self): |
+ well_known_values = ['Pri-High', 'Pri-Med', 'Pri-Low'] |
+ art = fake.MakeTestIssue(789, 1, 'sum 1', 'New', 111L, merged_into=200001) |
+ |
+ # Case 1: accessor generates no values. |
+ accessor = sorting._IndexOrLexicalList(well_known_values, [], 'pri', {}) |
+ self.assertEqual(sorting.MAX_STRING, accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual(sorting.MAX_STRING, neg_accessor(art)) |
+ |
+ # Case 2: A single well-known value |
+ art.labels = ['Pri-Med'] |
+ accessor = sorting._IndexOrLexicalList(well_known_values, [], 'pri', {}) |
+ self.assertEqual([1], accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual([-1], neg_accessor(art)) |
+ |
+ # Case 3: Multiple well-known and odd-ball values |
+ art.labels = ['Pri-zzz', 'Pri-Med', 'yyy', 'Pri-High'] |
+ accessor = sorting._IndexOrLexicalList(well_known_values, [], 'pri', {}) |
+ self.assertEqual([0, 1, 'zzz'], accessor(art)) |
+ neg_accessor = MakeDescending(accessor) |
+ self.assertEqual([sorting.DescendingValue('zzz'), -1, 0], |
+ neg_accessor(art)) |
+ |
+ def testComputeSortDirectives(self): |
+ config = tracker_pb2.ProjectIssueConfig() |
+ mr = testing_helpers.MakeMonorailRequest( |
+ path='/p/proj/issues/detail?id=123') |
+ self.assertEquals(['project', 'id'], |
+ sorting.ComputeSortDirectives(mr, config)) |
+ |
+ mr = testing_helpers.MakeMonorailRequest( |
+ path='/p/proj/issues/detail?id=123&sort=a b C') |
+ self.assertEquals(['a', 'b', 'c', 'project', 'id'], |
+ sorting.ComputeSortDirectives(mr, config)) |
+ |
+ config.default_sort_spec = 'id -reporter Owner' |
+ mr = testing_helpers.MakeMonorailRequest( |
+ path='/p/proj/issues/detail?id=123') |
+ self.assertEquals(['id', '-reporter', 'owner', 'project'], |
+ sorting.ComputeSortDirectives(mr, config)) |
+ |
+ mr = testing_helpers.MakeMonorailRequest( |
+ path='/p/proj/issues/detail?id=123&sort=A -b c -owner') |
+ self.assertEquals( |
+ ['a', '-b', 'c', '-owner', 'id', '-reporter', 'project'], |
+ sorting.ComputeSortDirectives(mr, config)) |
+ |
+ |
+if __name__ == '__main__': |
+ unittest.main() |