Repository: gratipay/gratipay.com

Issue:
Fix `safely_reserve_a_username`
This function keeps using a cursor after an `IntegrityError` exception is raised, that's invalid and raises another exception. See https://github.com/gratipay/gratipay.com/pull/2752#issuecomment-65266388.

Fix `safely_reserve_a_username`
This function keeps using a cursor after an `IntegrityError` exception is raised, that's invalid and raises another exception. See https://github.com/gratipay/gratipay.com/pull/2752#issuecomment-65266388.



Discussion:
Why are we seeing this on #2752? Should it be fixed on there or is it more general?

> Why are we seeing this on #2752? Should it be fixed on there or is it more general?

8538edcc6d299503c6cccf01ae893729dbdef144 fixes the problem that was causing the bug in `safely_reserve_a_username` to become apparent in #2752. That bug should be fixed in a separate PR.

Okay, cool.

Why are we seeing this on #2752? Should it be fixed on there or is it more general?

> Why are we seeing this on #2752? Should it be fixed on there or is it more general?

8538edcc6d299503c6cccf01ae893729dbdef144 fixes the problem that was causing the bug in `safely_reserve_a_username` to become apparent in #2752. That bug should be fixed in a separate PR.

Okay, cool.


Resolving patch:
diff --git a/gratipay/utils/username.py b/gratipay/utils/username.py
--- a/gratipay/utils/username.py
+++ b/gratipay/utils/username.py
@@ -38,6 +38,8 @@ def safely_reserve_a_username(cursor, gen_usernames=gen_random_usernames,
     The returned value is guaranteed to have been reserved in the database.
 
     """
+    cursor.execute("SAVEPOINT safely_reserve_a_username")
+
     seatbelt = 0
     for username in gen_usernames():
         seatbelt += 1
@@ -47,10 +49,13 @@ def safely_reserve_a_username(cursor, gen_usernames=gen_random_usernames,
         try:
             check = reserve(cursor, username)
         except IntegrityError:  # Collision, try again with another value.
+            cursor.execute("ROLLBACK TO safely_reserve_a_username")
             continue
         else:
             assert check == username
             break
     else:
         raise RanOutOfUsernameAttempts
+
+    cursor.execute("RELEASE safely_reserve_a_username")
     return username


Test patch:
diff --git a/tests/py/test_utils.py b/tests/py/test_utils.py
--- a/tests/py/test_utils.py
+++ b/tests/py/test_utils.py
@@ -133,9 +133,7 @@ def test_markdown_render_no_intra_emphasis(self):
         actual = markdown.render('Examples like this_one and this other_one.')
         assert expected == actual
 
-    @pytest.mark.xfail
     def test_srau_retries_work_with_db(self):
-        # XXX This is raising InternalError because the transaction is ended or something.
         self.make_participant('deadbeef')
         def gen_test_username():
             yield 'deadbeef'
@@ -143,14 +141,3 @@ def gen_test_username():
         with self.db.get_cursor() as cursor:
             username = safely_reserve_a_username(cursor, gen_test_username)
             assert username == 'deafbeef'
-
-    @pytest.mark.xfail
-    def test_srau_retries_cheese(self):
-        # XXX This is a simplified case of the above test.
-        with self.db.get_cursor() as cursor:
-            cursor.execute("INSERT INTO participants (username, username_lower) VALUES ('c', 'c')")
-            try:
-                cursor.execute("INSERT INTO participants (username, username_lower) VALUES ('c', 'c')")
-            except:
-                pass
-            cursor.execute("INSERT INTO participants (username, username_lower) VALUES ('c', 'c')")