Index: appengine/monorail/framework/test/framework_helpers_test.py |
diff --git a/appengine/monorail/framework/test/framework_helpers_test.py b/appengine/monorail/framework/test/framework_helpers_test.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..79ac20e650f8290ca8acb7b75b04ca4c68c4af92 |
--- /dev/null |
+++ b/appengine/monorail/framework/test/framework_helpers_test.py |
@@ -0,0 +1,442 @@ |
+# 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 the framework_helpers module.""" |
+ |
+import unittest |
+ |
+import mox |
+import time |
+ |
+from framework import framework_helpers |
+from framework import framework_views |
+from proto import project_pb2 |
+from services import service_manager |
+from testing import fake |
+from testing import testing_helpers |
+ |
+ |
+class HelperFunctionsTest(unittest.TestCase): |
+ |
+ def setUp(self): |
+ self.mox = mox.Mox() |
+ self.time = self.mox.CreateMock(framework_helpers.time) |
+ framework_helpers.time = self.time # Point to a mocked out time module. |
+ |
+ def tearDown(self): |
+ framework_helpers.time = time # Point back to the time module. |
+ self.mox.UnsetStubs() |
+ self.mox.ResetAll() |
+ |
+ def testRetryDecorator_ExceedFailures(self): |
+ class Tracker(object): |
+ func_called = 0 |
+ tracker = Tracker() |
+ |
+ # Use a function that always fails. |
+ @framework_helpers.retry(2, delay=1, backoff=2) |
+ def testFunc(tracker): |
+ tracker.func_called += 1 |
+ raise Exception('Failed') |
+ |
+ self.time.sleep(1).AndReturn(None) |
+ self.time.sleep(2).AndReturn(None) |
+ self.mox.ReplayAll() |
+ with self.assertRaises(Exception): |
+ testFunc(tracker) |
+ self.mox.VerifyAll() |
+ self.assertEquals(3, tracker.func_called) |
+ |
+ def testRetryDecorator_EventuallySucceed(self): |
+ class Tracker(object): |
+ func_called = 0 |
+ tracker = Tracker() |
+ |
+ # Use a function that succeeds on the 2nd attempt. |
+ @framework_helpers.retry(2, delay=1, backoff=2) |
+ def testFunc(tracker): |
+ tracker.func_called += 1 |
+ if tracker.func_called < 2: |
+ raise Exception('Failed') |
+ |
+ self.time.sleep(1).AndReturn(None) |
+ self.mox.ReplayAll() |
+ testFunc(tracker) |
+ self.mox.VerifyAll() |
+ self.assertEquals(2, tracker.func_called) |
+ |
+ def testGetRoleName(self): |
+ proj = project_pb2.Project() |
+ proj.owner_ids.append(111L) |
+ proj.committer_ids.append(222L) |
+ proj.contributor_ids.append(333L) |
+ |
+ self.assertEquals(None, framework_helpers.GetRoleName(set(), proj)) |
+ |
+ self.assertEquals( |
+ 'Owner', framework_helpers.GetRoleName({111L}, proj)) |
+ self.assertEquals( |
+ 'Committer', framework_helpers.GetRoleName({222L}, proj)) |
+ self.assertEquals( |
+ 'Contributor', framework_helpers.GetRoleName({333L}, proj)) |
+ |
+ self.assertEquals( |
+ 'Owner', |
+ framework_helpers.GetRoleName({111L, 222L, 999L}, proj)) |
+ self.assertEquals( |
+ 'Committer', |
+ framework_helpers.GetRoleName({222L, 333L, 999L}, proj)) |
+ self.assertEquals( |
+ 'Contributor', |
+ framework_helpers.GetRoleName({333L, 999L}, proj)) |
+ |
+ |
+class UrlFormattingTest(unittest.TestCase): |
+ """Tests for URL formatting.""" |
+ |
+ def setUp(self): |
+ self.services = service_manager.Services(user=fake.UserService()) |
+ |
+ def testFormatMovedProjectURL(self): |
+ """Project foo has been moved to bar. User is visiting /p/foo/...""" |
+ mr = testing_helpers.MakeMonorailRequest() |
+ mr.current_page_url = '/p/foo/' |
+ self.assertEqual( |
+ '/p/bar/', |
+ framework_helpers.FormatMovedProjectURL(mr, 'bar')) |
+ |
+ mr.current_page_url = '/p/foo/issues/list' |
+ self.assertEqual( |
+ '/p/bar/issues/list', |
+ framework_helpers.FormatMovedProjectURL(mr, 'bar')) |
+ |
+ mr.current_page_url = '/p/foo/issues/detail?id=123' |
+ self.assertEqual( |
+ '/p/bar/issues/detail?id=123', |
+ framework_helpers.FormatMovedProjectURL(mr, 'bar')) |
+ |
+ mr.current_page_url = '/p/foo/issues/detail?id=123#c7' |
+ self.assertEqual( |
+ '/p/bar/issues/detail?id=123#c7', |
+ framework_helpers.FormatMovedProjectURL(mr, 'bar')) |
+ |
+ def testFormatURL(self): |
+ mr = testing_helpers.MakeMonorailRequest() |
+ path = '/dude/wheres/my/car' |
+ url = framework_helpers.FormatURL(mr, path) |
+ self.assertEqual(path, url) |
+ |
+ def testFormatURLWithRecognizedParams(self): |
+ params = {} |
+ query = [] |
+ for name in framework_helpers.RECOGNIZED_PARAMS: |
+ params[name] = name |
+ query.append('%s=%s' % (name, name)) |
+ path = '/dude/wheres/my/car' |
+ expected = '%s?%s' % (path, '&'.join(query)) |
+ mr = testing_helpers.MakeMonorailRequest(path=expected) |
+ url = framework_helpers.FormatURL(mr, path) # No added params. |
+ self.assertEqual(expected, url) |
+ |
+ def testFormatURLWithKeywordArgs(self): |
+ params = {} |
+ query_pairs = [] |
+ for name in framework_helpers.RECOGNIZED_PARAMS: |
+ params[name] = name |
+ if name is not 'can' and name is not 'start': |
+ query_pairs.append('%s=%s' % (name, name)) |
+ path = '/dude/wheres/my/car' |
+ mr = testing_helpers.MakeMonorailRequest( |
+ path='%s?%s' % (path, '&'.join(query_pairs))) |
+ query_pairs.append('can=yep') |
+ query_pairs.append('start=486') |
+ query_string = '&'.join(query_pairs) |
+ expected = '%s?%s' % (path, query_string) |
+ url = framework_helpers.FormatURL(mr, path, can='yep', start=486) |
+ self.assertEqual(expected, url) |
+ |
+ def testFormatURLWithKeywordArgsAndID(self): |
+ params = {} |
+ query_pairs = [] |
+ query_pairs.append('id=200') # id should be the first parameter. |
+ for name in framework_helpers.RECOGNIZED_PARAMS: |
+ params[name] = name |
+ if name is not 'can' and name is not 'start': |
+ query_pairs.append('%s=%s' % (name, name)) |
+ path = '/dude/wheres/my/car' |
+ mr = testing_helpers.MakeMonorailRequest( |
+ path='%s?%s' % (path, '&'.join(query_pairs))) |
+ query_pairs.append('can=yep') |
+ query_pairs.append('start=486') |
+ query_string = '&'.join(query_pairs) |
+ expected = '%s?%s' % (path, query_string) |
+ url = framework_helpers.FormatURL(mr, path, can='yep', start=486, id=200) |
+ self.assertEqual(expected, url) |
+ |
+ def testFormatURLWithStrangeParams(self): |
+ mr = testing_helpers.MakeMonorailRequest(path='/foo?start=0') |
+ url = framework_helpers.FormatURL( |
+ mr, '/foo', r=0, path='/foo/bar', sketchy='/foo/ bar baz ') |
+ self.assertEqual( |
+ '/foo?start=0&path=/foo/bar&r=0&sketchy=/foo/%20bar%20baz%20', |
+ url) |
+ |
+ def testFormatAbsoluteURL(self): |
+ _request, mr = testing_helpers.GetRequestObjects( |
+ path='/p/proj/some-path', |
+ headers={'Host': 'www.test.com'}) |
+ self.assertEqual( |
+ 'http://www.test.com/p/proj/some/path', |
+ framework_helpers.FormatAbsoluteURL(mr, '/some/path')) |
+ |
+ def testFormatAbsoluteURL_CommonRequestParams(self): |
+ _request, mr = testing_helpers.GetRequestObjects( |
+ path='/p/proj/some-path?foo=bar&can=1', |
+ headers={'Host': 'www.test.com'}) |
+ self.assertEqual( |
+ 'http://www.test.com/p/proj/some/path?can=1', |
+ framework_helpers.FormatAbsoluteURL(mr, '/some/path')) |
+ self.assertEqual( |
+ 'http://www.test.com/p/proj/some/path', |
+ framework_helpers.FormatAbsoluteURL( |
+ mr, '/some/path', copy_params=False)) |
+ |
+ def testFormatAbsoluteURL_NoProject(self): |
+ path = '/some/path' |
+ _request, mr = testing_helpers.GetRequestObjects( |
+ headers={'Host': 'www.test.com'}, path=path) |
+ url = framework_helpers.FormatAbsoluteURL(mr, path, include_project=False) |
+ self.assertEqual(url, 'http://www.test.com/some/path') |
+ |
+ |
+class WordWrapSuperLongLinesTest(unittest.TestCase): |
+ |
+ def testEmptyLogMessage(self): |
+ msg = '' |
+ wrapped_msg = framework_helpers.WordWrapSuperLongLines(msg) |
+ self.assertEqual(wrapped_msg, '') |
+ |
+ def testShortLines(self): |
+ msg = 'one\ntwo\nthree\n' |
+ wrapped_msg = framework_helpers.WordWrapSuperLongLines(msg) |
+ expected = 'one\ntwo\nthree\n' |
+ self.assertEqual(wrapped_msg, expected) |
+ |
+ def testOneLongLine(self): |
+ msg = ('This is a super long line that just goes on and on ' |
+ 'and it seems like it will never stop because it is ' |
+ 'super long and it was entered by a user who had no ' |
+ 'familiarity with the return key.') |
+ wrapped_msg = framework_helpers.WordWrapSuperLongLines(msg) |
+ expected = ('This is a super long line that just goes on and on and it ' |
+ 'seems like it will never stop because it\n' |
+ 'is super long and it was entered by a user who had no ' |
+ 'familiarity with the return key.') |
+ self.assertEqual(wrapped_msg, expected) |
+ |
+ msg2 = ('This is a super long line that just goes on and on ' |
+ 'and it seems like it will never stop because it is ' |
+ 'super long and it was entered by a user who had no ' |
+ 'familiarity with the return key. ' |
+ 'This is a super long line that just goes on and on ' |
+ 'and it seems like it will never stop because it is ' |
+ 'super long and it was entered by a user who had no ' |
+ 'familiarity with the return key.') |
+ wrapped_msg2 = framework_helpers.WordWrapSuperLongLines(msg2) |
+ expected2 = ('This is a super long line that just goes on and on and it ' |
+ 'seems like it will never stop because it\n' |
+ 'is super long and it was entered by a user who had no ' |
+ 'familiarity with the return key. This is a\n' |
+ 'super long line that just goes on and on and it seems like ' |
+ 'it will never stop because it is super\n' |
+ 'long and it was entered by a user who had no familiarity ' |
+ 'with the return key.') |
+ self.assertEqual(wrapped_msg2, expected2) |
+ |
+ def testMixOfShortAndLong(self): |
+ msg = ('[Author: mpcomplete]\n' |
+ '\n' |
+ # Description on one long line |
+ 'Fix a memory leak in JsArray and JsObject for the IE and NPAPI ' |
+ 'ports. Each time you call GetElement* or GetProperty* to ' |
+ 'retrieve string or object token, the token would be leaked. ' |
+ 'I added a JsScopedToken to ensure that the right thing is ' |
+ 'done when the object leaves scope, depending on the platform.\n' |
+ '\n' |
+ 'R=zork\n' |
+ 'CC=google-gears-eng@googlegroups.com\n' |
+ 'DELTA=108 (52 added, 36 deleted, 20 changed)\n' |
+ 'OCL=5932446\n' |
+ 'SCL=5933728\n') |
+ wrapped_msg = framework_helpers.WordWrapSuperLongLines(msg) |
+ expected = ( |
+ '[Author: mpcomplete]\n' |
+ '\n' |
+ 'Fix a memory leak in JsArray and JsObject for the IE and NPAPI ' |
+ 'ports. Each time you call\n' |
+ 'GetElement* or GetProperty* to retrieve string or object token, the ' |
+ 'token would be leaked. I added\n' |
+ 'a JsScopedToken to ensure that the right thing is done when the ' |
+ 'object leaves scope, depending on\n' |
+ 'the platform.\n' |
+ '\n' |
+ 'R=zork\n' |
+ 'CC=google-gears-eng@googlegroups.com\n' |
+ 'DELTA=108 (52 added, 36 deleted, 20 changed)\n' |
+ 'OCL=5932446\n' |
+ 'SCL=5933728\n') |
+ self.assertEqual(wrapped_msg, expected) |
+ |
+ |
+class ComputeListDeltasTest(unittest.TestCase): |
+ |
+ def DoOne(self, old=None, new=None, added=None, removed=None): |
+ """Run one call to the target method and check expected results.""" |
+ actual_added, actual_removed = framework_helpers.ComputeListDeltas( |
+ old, new) |
+ self.assertItemsEqual(added, actual_added) |
+ self.assertItemsEqual(removed, actual_removed) |
+ |
+ def testEmptyLists(self): |
+ self.DoOne(old=[], new=[], added=[], removed=[]) |
+ self.DoOne(old=[1, 2], new=[], added=[], removed=[1, 2]) |
+ self.DoOne(old=[], new=[1, 2], added=[1, 2], removed=[]) |
+ |
+ def testUnchanged(self): |
+ self.DoOne(old=[1], new=[1], added=[], removed=[]) |
+ self.DoOne(old=[1, 2], new=[1, 2], added=[], removed=[]) |
+ self.DoOne(old=[1, 2], new=[2, 1], added=[], removed=[]) |
+ |
+ def testCompleteChange(self): |
+ self.DoOne(old=[1, 2], new=[3, 4], added=[3, 4], removed=[1, 2]) |
+ |
+ def testGeneralChange(self): |
+ self.DoOne(old=[1, 2], new=[2], added=[], removed=[1]) |
+ self.DoOne(old=[1], new=[1, 2], added=[2], removed=[]) |
+ self.DoOne(old=[1, 2], new=[2, 3], added=[3], removed=[1]) |
+ |
+ |
+class UserSettingsTest(unittest.TestCase): |
+ |
+ def testGatherUnifiedSettingsPageData(self): |
+ email_options = [] |
+ |
+ class UserSettingsStub(framework_helpers.UserSettings): |
+ |
+ # pylint: disable=unused-argument |
+ @classmethod |
+ def _GetEmailOptions(cls, user_view, conn_pool): |
+ return email_options |
+ |
+ mr = testing_helpers.MakeMonorailRequest() |
+ mr.auth.user_view = framework_views.UserView(100, 'user@invalid', True) |
+ mr.auth.user_view.profile_url = '/u/profile/url' |
+ page_data = UserSettingsStub.GatherUnifiedSettingsPageData( |
+ mr.auth.user_id, mr.auth.user_view, mr.auth.user_pb) |
+ |
+ expected_keys = [ |
+ 'api_request_reset', |
+ 'api_request_lifetime_limit', |
+ 'api_request_hard_limit', |
+ 'api_request_soft_limit', |
+ 'settings_user', |
+ 'settings_user_pb', |
+ 'settings_user_is_banned', |
+ 'settings_user_ignore_action_limits', |
+ 'self', |
+ 'project_creation_reset', |
+ 'issue_comment_reset', |
+ 'issue_attachment_reset', |
+ 'issue_bulk_edit_reset', |
+ 'project_creation_lifetime_limit', |
+ 'project_creation_soft_limit', |
+ 'project_creation_hard_limit', |
+ 'issue_comment_lifetime_limit', |
+ 'issue_comment_soft_limit', |
+ 'issue_comment_hard_limit', |
+ 'issue_attachment_lifetime_limit', |
+ 'issue_attachment_soft_limit', |
+ 'issue_attachment_hard_limit', |
+ 'issue_bulk_edit_lifetime_limit', |
+ 'issue_bulk_edit_hard_limit', |
+ 'issue_bulk_edit_soft_limit', |
+ 'profile_url_fragment', |
+ 'preview_on_hover', |
+ ] |
+ self.assertItemsEqual(expected_keys, page_data.keys()) |
+ |
+ self.assertEqual('profile/url', page_data['profile_url_fragment']) |
+ # TODO(jrobbins): Test action limit support |
+ |
+ # TODO(jrobbins): Test ProcessForm. |
+ |
+ |
+class MurmurHash3Test(unittest.TestCase): |
+ |
+ def testMurmurHash(self): |
+ test_data = [ |
+ ('', 0), |
+ ('agable@chromium.org', 4092810879), |
+ (u'jrobbins@chromium.org', 904770043), |
+ ('seanmccullough%google.com@gtempaccount.com', 1301269279), |
+ ('rmistry+monorail@chromium.org', 4186878788), |
+ ('jparent+foo@', 2923900874), |
+ ('@example.com', 3043483168), |
+ ] |
+ hashes = [framework_helpers.MurmurHash3_x86_32(x) |
+ for (x, _) in test_data] |
+ self.assertListEqual(hashes, [e for (_, e) in test_data]) |
+ |
+ def testMurmurHashWithSeed(self): |
+ test_data = [ |
+ ('', 1113155926, 2270882445), |
+ ('agable@chromium.org', 772936925, 3995066671), |
+ (u'jrobbins@chromium.org', 1519359761, 1273489513), |
+ ('seanmccullough%google.com@gtempaccount.com', 49913829, 1202521153), |
+ ('rmistry+monorail@chromium.org', 314860298, 3636123309), |
+ ('jparent+foo@', 195791379, 332453977), |
+ ('@example.com', 521490555, 257496459), |
+ ] |
+ hashes = [framework_helpers.MurmurHash3_x86_32(x, s) |
+ for (x, s, _) in test_data] |
+ self.assertListEqual(hashes, [e for (_, _, e) in test_data]) |
+ |
+ |
+class MakeRandomKeyTest(unittest.TestCase): |
+ |
+ def testMakeRandomKey_Normal(self): |
+ key1 = framework_helpers.MakeRandomKey() |
+ key2 = framework_helpers.MakeRandomKey() |
+ self.assertEqual(128, len(key1)) |
+ self.assertEqual(128, len(key2)) |
+ self.assertNotEqual(key1, key2) |
+ |
+ def testMakeRandomKey_Length(self): |
+ key = framework_helpers.MakeRandomKey() |
+ self.assertEqual(128, len(key)) |
+ key16 = framework_helpers.MakeRandomKey(length=16) |
+ self.assertEqual(16, len(key16)) |
+ |
+ def testMakeRandomKey_Chars(self): |
+ key = framework_helpers.MakeRandomKey(chars='a', length=4) |
+ self.assertEqual('aaaa', key) |
+ |
+ |
+class IsServiceAccountTest(unittest.TestCase): |
+ |
+ def testIsServiceAccount(self): |
+ appspot = 'abc@appspot.gserviceaccount.com' |
+ developer = '@developer.gserviceaccount.com' |
+ bugdroid = 'bugdroid1@chromium.org' |
+ user = 'test@example.com' |
+ |
+ self.assertTrue(framework_helpers.IsServiceAccount(appspot)) |
+ self.assertTrue(framework_helpers.IsServiceAccount(developer)) |
+ self.assertTrue(framework_helpers.IsServiceAccount(bugdroid)) |
+ self.assertFalse(framework_helpers.IsServiceAccount(user)) |
+ |
+ |
+if __name__ == '__main__': |
+ unittest.main() |