Repository: oppia/oppia

Issue:
Flaky backend admin tests
**Describe the bug**
The admin test UpdateUsernameHandlerTest seems to be flaking. The test passes without any problem in the local system. I have attached the logs.
[build_1046_step_104_container_0.txt](https://github.com/oppia/oppia/files/4683844/build_1046_step_104_container_0.txt)





Discussion:
cc. @oppia/core-maintainers 

Edit: test introduced in #9191
@iamprayush This is a priority, try to resolve it ASAP please, in the test please mock the `datetime` value so that it doesn't change and the id of the mdoel is always the same.
Re-opening because this occured again. See [build](https://app.circleci.com/pipelines/github/kevintab95/oppia/156/workflows/438dcb95-f028-42b6-9701-5a9ce49df3a4/jobs/1576/steps).
@iamprayush Can you take a look again please?

Resolving patch:
diff --git a/core/domain/user_services.py b/core/domain/user_services.py
--- a/core/domain/user_services.py
+++ b/core/domain/user_services.py
@@ -23,9 +23,7 @@
 import hashlib
 import imghdr
 import logging
-import math
 import re
-import time
 
 from constants import constants
 from core.domain import role_services
@@ -2022,8 +2020,8 @@ def log_username_change(committer_id, old_username, new_username):
         new_username: str. The new username that the current one is being
             changed to.
     """
-    model_id = '%s.%s' % (committer_id, int(math.floor(time.time())))
 
+    model_id = '%s.%d' % (committer_id, utils.get_current_time_in_millisecs())
     audit_models.UsernameChangeAuditModel(
         id=model_id, committer_id=committer_id, old_username=old_username,
         new_username=new_username).put()


Test patch:
diff --git a/core/controllers/admin_test.py b/core/controllers/admin_test.py
--- a/core/controllers/admin_test.py
+++ b/core/controllers/admin_test.py
@@ -17,9 +17,7 @@
 from __future__ import absolute_import  # pylint: disable=import-only-modules
 from __future__ import unicode_literals  # pylint: disable=import-only-modules
 
-import datetime
 import logging
-import math
 
 from constants import constants
 from core import jobs
@@ -48,6 +46,7 @@
 from core.platform.taskqueue import gae_taskqueue_services as taskqueue_services
 from core.tests import test_utils
 import feconf
+import utils
 
 (exp_models, job_models, opportunity_models, audit_models) = (
     models.Registry.import_models(
@@ -1249,11 +1248,18 @@ def test_update_username_creates_audit_model(self):
         user_id = self.get_user_id_from_email(self.ADMIN_EMAIL)
         csrf_token = self.get_new_csrf_token()
 
-        current_datetime = datetime.datetime.utcnow()
-        with self.mock_datetime_utcnow(current_datetime):
-            # To get the current time in seconds.
-            time_of_query = (datetime.datetime.utcnow() - datetime.datetime(
-                1970, 1, 1)).total_seconds()
+        creation_time_in_millisecs = utils.get_current_time_in_millisecs()
+        mock_get_current_time_in_millisecs = lambda: creation_time_in_millisecs
+        # Since the UsernameChangeAuditModel's ID is formed from the user ID and
+        # a millisecond timestamp we need to make sure that
+        # get_current_time_in_millisecs returns the same value as we have saved
+        # into current_time_in_millisecs. If we don't force the same value via
+        # swap flakes can occur, since as the time flows the saved milliseconds
+        # can differ from the milliseconds saved into the
+        # UsernameChangeAuditModel's ID.
+        with self.swap(
+            utils, 'get_current_time_in_millisecs',
+            mock_get_current_time_in_millisecs):
             self.put_json(
                 '/updateusernamehandler',
                 payload={
@@ -1265,8 +1271,7 @@ def test_update_username_creates_audit_model(self):
             audit_models.UsernameChangeAuditModel.has_reference_to_user_id(
                 user_id))
 
-        model_id = '%s.%s' % (user_id, int(math.floor(time_of_query)))
-
+        model_id = '%s.%d' % (user_id, creation_time_in_millisecs)
         username_change_audit_model = (
             audit_models.UsernameChangeAuditModel.get(model_id))
 
diff --git a/core/domain/prod_validation_jobs_one_off_test.py b/core/domain/prod_validation_jobs_one_off_test.py
--- a/core/domain/prod_validation_jobs_one_off_test.py
+++ b/core/domain/prod_validation_jobs_one_off_test.py
@@ -494,7 +494,8 @@ def setUp(self):
         admin_model.role = feconf.ROLE_ID_ADMIN
         admin_model.put()
 
-        model_id = '%s.%s' % (self.admin_id, int(math.floor(time.time())))
+        model_id = (
+            '%s.%d' % (self.admin_id, utils.get_current_time_in_millisecs()))
         self.model_instance = audit_models.UsernameChangeAuditModel(
             id=model_id, committer_id=self.admin_id,
             old_username=USER_NAME, new_username='new')
@@ -549,8 +550,8 @@ def test_model_with_non_existent_user_id(self):
         run_job_and_check_output(self, expected_output)
 
     def test_model_with_invalid_id(self):
-        model_invalid_id = '%s.%s' % (
-            int(math.floor(time.time())), self.admin_id)
+        model_invalid_id = (
+            '%d.%s' % (utils.get_current_time_in_millisecs(), self.admin_id))
         model_instance_with_invalid_id = audit_models.UsernameChangeAuditModel(
             id=model_invalid_id, committer_id=self.admin_id,
             old_username=USER_NAME, new_username='new')