Repository: gratipay/gratipay.com

Issue:
Username change fails silently
When you change your username, you're querying [`username.json.spt`](https://github.com/gittip/www.gittip.com/blob/0464c57465aed49a95a2c546f0d9987ad5b9b3fa/www/%25username/username.json.spt). If the desired username is invalid, we respond back with a user-friendly message (though the UI is ugly). Unfortunately, this behavior is currently broken because it returns HTML instead of the expected JSON.

[IRC](https://botbot.me/freenode/gittip/msg/9518377/), from working on #1849

Thanks to @thomasboyt for [reporting the problem](https://botbot.me/freenode/gittip/msg/9517625/) :heart:

The Aspen ticket for this is: gittip/aspen-python#279

Username change fails silently
When you change your username, you're querying [`username.json.spt`](https://github.com/gittip/www.gittip.com/blob/0464c57465aed49a95a2c546f0d9987ad5b9b3fa/www/%25username/username.json.spt). If the desired username is invalid, we respond back with a user-friendly message (though the UI is ugly). Unfortunately, this behavior is currently broken because it returns HTML instead of the expected JSON.

[IRC](https://botbot.me/freenode/gittip/msg/9518377/), from working on #1849

Thanks to @thomasboyt for [reporting the problem](https://botbot.me/freenode/gittip/msg/9517625/) :heart:

The Aspen ticket for this is: gittip/aspen-python#279



Discussion:
It seems that fixing this [will take a new Aspen release](https://botbot.me/freenode/gittip/msg/9519196/). If I'd written tests that actually checked the response instead of just checking the HTTP error code, we could have caught this sooner :sweat:

Note to self: apply this diff as per https://github.com/gittip/www.gittip.com/pull/1849#discussion_r8732884

``` diff
diff --git a/gittip/exceptions.py b/gittip/exceptions.py
index 0ed3158..c07f850 100644
--- a/gittip/exceptions.py
+++ b/gittip/exceptions.py
@@ -17,10 +17,8 @@ class UsernameIsEmpty(ProblemChangingUsername):
 class UsernameTooLong(ProblemChangingUsername):
     msg = "The username '{}' is too long."

-# Not passing the potentially unicode characters back because of:
-# https://github.com/gittip/aspen-python/issues/177
 class UsernameContainsInvalidCharacters(ProblemChangingUsername):
-    msg = "That username contains invalid characters."
+    msg = "The username '{}' contains invalid characters."

 class UsernameIsRestricted(ProblemChangingUsername):
     msg = "The username '{}' is restricted."e username '{}' is restricted."
```

Made some progress on the Aspen side of this today. My proposal is in https://github.com/gittip/aspen-python/pull/288.

@whit537, @pjz I believe the fix is merged in aspen. Could we please get a release? It would also bring in the updated packaging which would unblock #1834.

Aspen v0.29 released.

It seems that fixing this [will take a new Aspen release](https://botbot.me/freenode/gittip/msg/9519196/). If I'd written tests that actually checked the response instead of just checking the HTTP error code, we could have caught this sooner :sweat:

Note to self: apply this diff as per https://github.com/gittip/www.gittip.com/pull/1849#discussion_r8732884

``` diff
diff --git a/gittip/exceptions.py b/gittip/exceptions.py
index 0ed3158..c07f850 100644
--- a/gittip/exceptions.py
+++ b/gittip/exceptions.py
@@ -17,10 +17,8 @@ class UsernameIsEmpty(ProblemChangingUsername):
 class UsernameTooLong(ProblemChangingUsername):
     msg = "The username '{}' is too long."

-# Not passing the potentially unicode characters back because of:
-# https://github.com/gittip/aspen-python/issues/177
 class UsernameContainsInvalidCharacters(ProblemChangingUsername):
-    msg = "That username contains invalid characters."
+    msg = "The username '{}' contains invalid characters."

 class UsernameIsRestricted(ProblemChangingUsername):
     msg = "The username '{}' is restricted."e username '{}' is restricted."
```

Made some progress on the Aspen side of this today. My proposal is in https://github.com/gittip/aspen-python/pull/288.

@whit537, @pjz I believe the fix is merged in aspen. Could we please get a release? It would also bring in the updated packaging which would unblock #1834.

Aspen v0.29 released.


Resolving patch:
diff --git a/gittip/exceptions.py b/gittip/exceptions.py
--- a/gittip/exceptions.py
+++ b/gittip/exceptions.py
@@ -18,10 +18,8 @@ class UsernameIsEmpty(ProblemChangingUsername):
 class UsernameTooLong(ProblemChangingUsername):
     msg = "The username '{}' is too long."
 
-# Not passing the potentially unicode characters back because of:
-# https://github.com/gittip/aspen-python/issues/177
 class UsernameContainsInvalidCharacters(ProblemChangingUsername):
-    msg = "That username contains invalid characters."
+    msg = "The username '{}' contains invalid characters."
 
 class UsernameIsRestricted(ProblemChangingUsername):
     msg = "The username '{}' is restricted."


Test patch:
diff --git a/tests/defaults.env b/tests/defaults.env
--- a/tests/defaults.env
+++ b/tests/defaults.env
@@ -51,3 +51,5 @@ GOOGLE_ANALYTICS_ID=
 SENTRY_DSN=
 LOG_BUSY_THREADS_EVERY=0
 LOG_METRICS=0
+
+ASPEN_SHOW_TRACEBACKS=1
diff --git a/tests/test_username_json.py b/tests/test_username_json.py
--- a/tests/test_username_json.py
+++ b/tests/test_username_json.py
@@ -7,41 +7,51 @@
 
 class Tests(Harness):
 
-    def change_username(self, new_username, auth_as='alice', expecting_error=False):
-        self.make_participant('alice') if auth_as is not None else ''
-        method = self.client.POST if not expecting_error else self.client.PxST
-        return method("/alice/username.json", {'username': new_username}, auth_as=auth_as)
+    def change_username(self, new_username, auth_as='alice', weird=False):
+        if auth_as:
+            self.make_participant(auth_as)
 
+        if weird:
+            post, args = self.client.PxST, {}
+        else:
+            post, args = self.client.POST, {'raise_immediately': False}
+
+        r = post('/alice/username.json', {'username': new_username},
+                                         auth_as=auth_as, **args)
+        return r.code, r.body if weird else json.loads(r.body)
 
     def test_participant_can_change_their_username(self):
-        response = self.change_username("bob")
-        actual = json.loads(response.body)['username']
-        assert actual == "bob"
+        code, body = self.change_username("bob")
+        assert code == 200
+        assert body['username'] == "bob"
 
     def test_anonymous_gets_404(self):
-        response = self.change_username("bob", auth_as=None, expecting_error=True)
-        assert response.code == 404
+        code, body = self.change_username("bob", auth_as=None, weird=True)
+        assert code == 404
 
     def test_empty(self):
-        response = self.change_username('      ', expecting_error=True)
-        assert response.code == 400
+        code, body = self.change_username('      ')
+        assert code == 400
+        assert body['error_message_long'] == "You need to provide a username!"
 
     def test_invalid(self):
-        response = self.change_username("\u2034".encode('utf8'), expecting_error=True)
-        assert response.code == 400
+        code, body = self.change_username("§".encode('utf8'))
+        assert code == 400
+        assert body['error_message_long'] == "The username '§' contains invalid characters."
 
     def test_restricted_username(self):
-        response = self.change_username("assets", expecting_error=True)
-        assert response.code == 400
+        code, body = self.change_username("assets")
+        assert code == 400
+        assert body['error_message_long'] == "The username 'assets' is restricted."
 
     def test_unavailable(self):
         self.make_participant("bob")
-        response = self.change_username("bob", expecting_error=True)
-        assert response.code == 400
+        code, body = self.change_username("bob")
+        assert code == 400
+        assert body['error_message_long'] == "The username 'bob' is already taken."
 
     def test_too_long(self):
-        self.make_participant("bob")
-        response = self.change_username("I am way too long, and you know it, "
-                                        "and I know it, and the American "
-                                        "people know it.", expecting_error=True)
-        assert response.code == 400
+        username = "I am way too long, and you know it, and the American people know it."
+        code, body = self.change_username(username)
+        assert code == 400
+        assert body['error_message_long'] == "The username '%s' is too long." % username