feat: add pronoun privacy option (#6773)

This commit contains UI changes, tests and migrations for a feature
that lets users optionally hide their pronouns from the general
public. This is useful if a person wants to disclose that
information to a smaller set of people on a local instance
belonging to a local community/association.

Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Beowulf <beowulf@beocode.eu>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6773
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
Co-committed-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
This commit is contained in:
Panagiotis "Ivory" Vasilopoulos 2025-02-15 13:07:15 +00:00 committed by Gusted
parent 7104c73c96
commit a1486b0ee4
17 changed files with 158 additions and 25 deletions

View file

@ -438,8 +438,16 @@ func TestUserHints(t *testing.T) {
func TestUserPronouns(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
// user1 is admin, using user2 and user10 respectively instead.
// This is explicitly mentioned here because of the unconventional
// variable naming scheme.
firstUserSession := loginUser(t, "user2")
firstUserToken := getTokenForLoggedInUser(t, firstUserSession, auth_model.AccessTokenScopeWriteUser)
// This user has the HidePronouns setting enabled.
// Check the fixture!
secondUserSession := loginUser(t, "user10")
secondUserToken := getTokenForLoggedInUser(t, secondUserSession, auth_model.AccessTokenScopeWriteUser)
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
adminSession := loginUser(t, adminUser.Name)
@ -449,8 +457,10 @@ func TestUserPronouns(t *testing.T) {
t.Run("user", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
// secondUserToken was chosen arbitrarily and should have no impact.
// See next comment.
req := NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(secondUserToken)
resp := firstUserSession.MakeRequest(t, req, http.StatusOK)
// We check the raw JSON, because we want to test the response, not
// what it decodes into. Contents doesn't matter, we're testing the
@ -468,16 +478,22 @@ func TestUserPronouns(t *testing.T) {
// what it decodes into. Contents doesn't matter, we're testing the
// presence only.
assert.Contains(t, resp.Body.String(), `"pronouns":`)
req = NewRequest(t, "GET", "/api/v1/users/user10")
resp = MakeRequest(t, req, http.StatusOK)
// Same deal here.
assert.Contains(t, resp.Body.String(), `"pronouns":`)
})
t.Run("user/settings", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Set pronouns first
// Set pronouns first for user2
pronouns := "they/them"
req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
Pronouns: &pronouns,
}).AddTokenAuth(token)
}).AddTokenAuth(firstUserToken)
resp := MakeRequest(t, req, http.StatusOK)
// Verify the response
@ -486,7 +502,7 @@ func TestUserPronouns(t *testing.T) {
assert.Equal(t, pronouns, user.Pronouns)
// Verify retrieving the settings again
req = NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(token)
req = NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(firstUserToken)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &user)
@ -497,22 +513,40 @@ func TestUserPronouns(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Set the pronouns for user2
pronouns := "she/her"
pronouns := "he/him"
req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{
Pronouns: &pronouns,
}).AddTokenAuth(adminToken)
resp := MakeRequest(t, req, http.StatusOK)
// Verify the API response
var user *api.User
DecodeJSON(t, resp, &user)
assert.Equal(t, pronouns, user.Pronouns)
var user2 *api.User
DecodeJSON(t, resp, &user2)
assert.Equal(t, pronouns, user2.Pronouns)
// Verify via user2 too
req = NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(token)
// Verify via user2
req = NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(firstUserToken)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &user)
assert.Equal(t, pronouns, user.Pronouns)
DecodeJSON(t, resp, &user2)
assert.Equal(t, pronouns, user2.Pronouns) // TODO: This fails for some reason
// Set the pronouns for user10
pronouns = "he/him"
req = NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user10", &api.EditUserOption{
Pronouns: &pronouns,
}).AddTokenAuth(adminToken)
resp = MakeRequest(t, req, http.StatusOK)
// Verify the API response
var user10 *api.User
DecodeJSON(t, resp, &user10)
assert.Equal(t, pronouns, user10.Pronouns)
// Verify via user10
req = NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(secondUserToken)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &user10)
assert.Equal(t, pronouns, user10.Pronouns)
})
})
@ -520,10 +554,10 @@ func TestUserPronouns(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Set the pronouns to a known state via the API
pronouns := "she/her"
pronouns := "they/them"
req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
Pronouns: &pronouns,
}).AddTokenAuth(token)
}).AddTokenAuth(firstUserToken)
MakeRequest(t, req, http.StatusOK)
t.Run("profile view", func(t *testing.T) {
@ -534,14 +568,14 @@ func TestUserPronouns(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body)
userNameAndPronouns := strings.TrimSpace(htmlDoc.Find(".profile-avatar-name .username").Text())
assert.Contains(t, userNameAndPronouns, pronouns)
assert.NotContains(t, userNameAndPronouns, pronouns)
})
t.Run("settings", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user/settings")
resp := session.MakeRequest(t, req, http.StatusOK)
resp := firstUserSession.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
// Check that the field is present
@ -550,12 +584,12 @@ func TestUserPronouns(t *testing.T) {
assert.Equal(t, pronouns, pronounField)
// Check that updating the field works
newPronouns := "they/them"
newPronouns := "she/her"
req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings"),
"_csrf": GetCSRF(t, firstUserSession, "/user/settings"),
"pronouns": newPronouns,
})
session.MakeRequest(t, req, http.StatusSeeOther)
firstUserSession.MakeRequest(t, req, http.StatusSeeOther)
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
assert.Equal(t, newPronouns, user2.Pronouns)