fix(web): allow clearing the full name in profile settings (#40186)

Clearing "Full Name" in the profile settings shows success but keeps the
old value: since #31959 it is only updated when non-empty, because the
input is disabled (not submitted) when `change_full_name` is disabled.

Use `*string` for the field, so a missing field keeps the value and an
empty one clears it. With `change_full_name` disabled, any submitted
value (including empty) is now rejected. Not `optional.Option[string]`,
because `MaxSize` would count slice elements instead of characters.

Fixes #40185

---
AI disclosure: written with Claude Code (claude-opus-5-5) and tested
locally.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
authored and GitHub committed 2026-11-04 01:42:41 +00:00
1 parent 4d00ed2a1e
commit 666e6e2d17
10 files changed
+206 -193

No files matched your search

+2
View File
@@ -11,6 +11,8 @@ import "strconv"
// * non-existing value are represented as an empty slice (None)
// * existing value is represented as a slice with one element (Some)
// * multiple values are represented as a slice with multiple elements (Some), the Value is the first element (not well-defined in this case)
//
// Attention: Option[T] doesn't work with some web middleware binding rules like "MaxSize" at the moment.
type Option[T any] []T
func None[T any]() Option[T] {
+19 -35
View File
@@ -54,71 +54,55 @@ func Profile(ctx *context.Context) {
// ProfilePost response for change user's profile
func ProfilePost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings_title")
ctx.Data["PageIsSettingsProfile"] = true
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplSettingsProfile)
form := context.GetFetchActionForm[*forms.UpdateProfileForm](ctx)
if form == nil {
return
}
form := web.GetForm[*forms.UpdateProfileForm](ctx)
if form.Name != "" {
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureChangeUsername) {
ctx.Flash.Error(ctx.Tr("user.form.change_username_disabled"))
ctx.Redirect(setting.AppSubURL + "/user/settings")
ctx.JSONErrorWithField(ctx.Tr("form.change_username_disabled"), "Name")
return
}
if err := user_service.RenameUser(ctx, ctx.Doer, form.Name, ctx.Doer); err != nil {
switch {
case user_model.IsErrUserIsNotLocal(err):
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
ctx.JSONErrorWithField(ctx.Tr("form.username_change_not_local_user"), "Name")
case user_model.IsErrUserAlreadyExist(err):
ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
ctx.JSONErrorWithField(ctx.Tr("form.username_been_taken"), "Name")
case db.IsErrNameReserved(err):
ctx.Flash.Error(ctx.Tr("user.form.name_reserved", form.Name))
ctx.JSONErrorWithField(ctx.Tr("user.form.name_reserved", form.Name), "Name")
case db.IsErrNamePatternNotAllowed(err):
ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", form.Name))
ctx.JSONErrorWithField(ctx.Tr("user.form.name_pattern_not_allowed", form.Name), "Name")
case db.IsErrNameCharsNotAllowed(err):
ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", form.Name))
ctx.JSONErrorWithField(ctx.Tr("user.form.name_chars_not_allowed", form.Name), "Name")
default:
ctx.ServerError("RenameUser", err)
return
ctx.JSONErrorAuto(err)
}
ctx.Redirect(setting.AppSubURL + "/user/settings")
return
}
}
opts := &user_service.UpdateOptions{
if form.FullName != nil && user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureChangeFullName) {
ctx.JSONErrorWithField(ctx.Tr("form.change_full_name_disabled"), "FullName")
return
}
if err := user_service.UpdateUser(ctx, ctx.Doer, &user_service.UpdateOptions{
KeepEmailPrivate: optional.Some(form.KeepEmailPrivate),
FullName: optional.FromPtr(form.FullName),
Description: optional.Some(form.Description),
Website: optional.Some(form.Website),
Location: optional.Some(form.Location),
Visibility: optional.Some(form.Visibility),
KeepActivityPrivate: optional.Some(form.KeepActivityPrivate),
}
if form.FullName != "" {
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureChangeFullName) {
ctx.Flash.Error(ctx.Tr("user.form.change_full_name_disabled"))
ctx.Redirect(setting.AppSubURL + "/user/settings")
return
}
opts.FullName = optional.Some(form.FullName)
}
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
ctx.ServerError("UpdateUser", err)
}); err != nil {
ctx.JSONErrorAuto(err)
return
}
log.Trace("User settings updated: %s", ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.update_profile_success"))
ctx.Redirect(setting.AppSubURL + "/user/settings")
ctx.JSONRedirect(setting.AppSubURL + "/user/settings")
}
// UpdateAvatarSetting update user's avatar
+1 -1
View File
@@ -635,7 +635,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Group("/user/settings", func() {
m.Get("", user_setting.Profile)
m.Post("", web.Bind[*forms.UpdateProfileForm](), user_setting.ProfilePost)
m.Post("", user_setting.ProfilePost)
m.Post("/update_preferences", user_setting.UpdatePreferences)
m.Get("/change_password", auth.MustChangePassword)
m.Post("/change_password", web.Bind[*forms.MustChangePasswordForm](), auth.MustChangePasswordPost)
+2 -2
View File
@@ -160,8 +160,8 @@ type IntrospectTokenForm struct {
// UpdateProfileForm form for updating profile
type UpdateProfileForm struct {
middleware.FormDefaultValidator
Name string `binding:"Username;MaxSize(40)"`
FullName string `binding:"MaxSize(100)"`
Name string `binding:"Username;MaxSize(40)"`
FullName *string `binding:"MaxSize(100)"`
KeepEmailPrivate bool
Website string `binding:"ValidSiteUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
+1 -1
View File
@@ -5,7 +5,7 @@
</h4>
<div class="ui attached segment">
<p>{{ctx.Locale.Tr "settings.profile_desc"}}</p>
<form class="ui form" action="{{.Link}}" method="post" data-global-init="initUserSettingsForm">
<form class="ui form form-fetch-action" action="{{.Link}}" method="post" data-global-init="initUserSettingsForm">
<div class="required field {{if .Err_Name}}error{{end}}">
<label for="username">{{ctx.Locale.Tr "username"}}
<span class="tw-text-red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span>
+2
View File
@@ -9,9 +9,11 @@ test('update profile biography', async ({page, request}) => {
await page.goto('/user/settings');
await page.getByLabel('Biography').fill(bio);
await page.getByRole('button', {name: 'Update Profile'}).click();
await expect(page.getByText('Your profile has been updated.', {exact: true})).toBeVisible();
await expect(page.getByLabel('Biography')).toHaveValue(bio);
await page.getByLabel('Biography').fill('');
await page.getByRole('button', {name: 'Update Profile'}).click();
await expect(page.getByText('Your profile has been updated.', {exact: true})).toBeVisible();
await expect(page.getByLabel('Biography')).toHaveValue('');
});
@@ -785,7 +785,7 @@ func TestPackageContainer(t *testing.T) {
"email": "user2@example.com",
"language": "en-US",
})
session.MakeRequest(t, req, http.StatusSeeOther)
session.MakeRequest(t, req, http.StatusOK)
t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName))
@@ -794,6 +794,6 @@ func TestPackageContainer(t *testing.T) {
"email": "user2@example.com",
"language": "en-US",
})
session.MakeRequest(t, req, http.StatusSeeOther)
session.MakeRequest(t, req, http.StatusOK)
})
}
+1 -1
View File
@@ -53,7 +53,7 @@ func testPrivateActivityHelperEnablePrivateActivity(t *testing.T) {
"language": "en-US",
"keep_activity_private": "1",
})
session.MakeRequest(t, req, http.StatusSeeOther)
session.MakeRequest(t, req, http.StatusOK)
}
func testPrivateActivityHelperHasVisibleActivitiesInHTMLDoc(htmlDoc *HTMLDoc) bool {
+175 -32
View File
@@ -10,14 +10,36 @@ import (
auth_model "gitea.dev/models/auth"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/container"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
"gitea.dev/modules/translation"
"gitea.dev/tests"
"github.com/stretchr/testify/assert"
)
func TestUserSettings(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("RevokeOAuth2GrantOfOtherUser", testRevokeOAuth2GrantOfOtherUser)
t.Run("Account", testUserSettingsAccount)
t.Run("UpdatePassword", testUserSettingsUpdatePassword)
t.Run("UpdateEmail", testUserSettingsUpdateEmail)
t.Run("DeleteEmail", testUserSettingsDeleteEmail)
t.Run("Delete", testUserSettingsDelete)
t.Run("Pages", testUserSettingsPages)
t.Run("Security", testUserSettingsSecurity)
t.Run("Applications", testUserSettingsApplications)
t.Run("Keys", testUserSettingsKeys)
t.Run("Secrets", testUserSettingsSecrets)
t.Run("RenameInvalidUsername", testRenameInvalidUsername)
t.Run("RenameReservedUsername", testRenameReservedUsername)
t.Run("ChangeFullName", testChangeFullName)
t.Run("LocationMapLink", testUserLocationMapLink)
t.Run("RenameUsername", testRenameUsername)
}
// Validate that each navbar setting is correct. This checks that the
// appropriate context is passed everywhere the navbar is rendered
func assertNavbar(t *testing.T, doc *HTMLDoc) {
@@ -56,9 +78,7 @@ func WithDisabledFeatures(t *testing.T, features ...string) {
})
}
func TestRevokeOAuth2GrantOfOtherUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testRevokeOAuth2GrantOfOtherUser(t *testing.T) {
// grant 1 belongs to user1, so user2 must not be able to act on it
session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/1/revoke/1", nil)
@@ -68,9 +88,7 @@ func TestRevokeOAuth2GrantOfOtherUser(t *testing.T) {
assert.EqualValues(t, 1, grant.UserID)
}
func TestUserSettingsAccount(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsAccount(t *testing.T) {
for _, testCase := range []struct {
name string
features []string
@@ -123,9 +141,7 @@ func TestUserSettingsAccount(t *testing.T) {
})
}
func TestUserSettingsUpdatePassword(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsUpdatePassword(t *testing.T) {
t.Run("enabled", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -148,9 +164,7 @@ func TestUserSettingsUpdatePassword(t *testing.T) {
})
}
func TestUserSettingsUpdateEmail(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsUpdateEmail(t *testing.T) {
t.Run("credentials disabled", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -183,9 +197,7 @@ func TestUserSettingsUpdateEmail(t *testing.T) {
}
}
func TestUserSettingsDeleteEmail(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsDeleteEmail(t *testing.T) {
t.Run("credentials disabled", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -196,9 +208,7 @@ func TestUserSettingsDeleteEmail(t *testing.T) {
})
}
func TestUserSettingsDelete(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsDelete(t *testing.T) {
t.Run("deletion disabled", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -210,10 +220,10 @@ func TestUserSettingsDelete(t *testing.T) {
})
}
func TestUserSettingsPages(t *testing.T) {
func testUserSettingsPages(t *testing.T) {
for _, page := range []string{"appearance", "packages", "packages/rules/add", "organization", "repos", "blocked_users"} {
t.Run(page, func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user/settings/"+page)
@@ -225,9 +235,7 @@ func TestUserSettingsPages(t *testing.T) {
}
}
func TestUserSettingsSecurity(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsSecurity(t *testing.T) {
for _, testCase := range []struct {
name string
feature string
@@ -261,9 +269,7 @@ func TestUserSettingsSecurity(t *testing.T) {
})
}
func TestUserSettingsApplications(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsApplications(t *testing.T) {
t.Run("Applications", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -342,9 +348,7 @@ func TestUserSettingsApplications(t *testing.T) {
})
}
func TestUserSettingsKeys(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsKeys(t *testing.T) {
for _, testCase := range []struct {
name string
feature string
@@ -384,9 +388,7 @@ func TestUserSettingsKeys(t *testing.T) {
})
}
func TestUserSettingsSecrets(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testUserSettingsSecrets(t *testing.T) {
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user/settings/actions/secrets")
if setting.Actions.Enabled {
@@ -398,3 +400,144 @@ func TestUserSettingsSecrets(t *testing.T) {
session.MakeRequest(t, req, http.StatusNotFound)
}
}
func testRenameInvalidUsername(t *testing.T) {
invalidUsernames := []string{
"%2f*",
"%2f.",
"%2f..",
"%00",
"thisHas ASpace",
"p<A>tho>lo<gical",
".",
"..",
".well-known",
".abc",
"abc.",
"a..bc",
"a...bc",
"a.-bc",
"a._bc",
"a_-bc",
"a/bc",
"☁️",
"-",
"--diff",
"-im-here",
"a space",
}
session := loginUser(t, "user2")
for _, invalidUsername := range invalidUsernames {
t.Logf("Testing username %s", invalidUsername)
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": invalidUsername,
"email": "user2@example.com",
})
resp := session.MakeRequest(t, req, http.StatusBadRequest)
data := DecodeJSON(t, resp, map[string]any{})
assert.Equal(t, []any{"Name"}, data["errorFields"])
assert.Contains(t,
data["errorMessage"],
translation.NewLocale("en-US").TrString("form.username_error", "Name"),
)
unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername})
}
}
func testRenameReservedUsername(t *testing.T) {
// ".", "..", ".well-known" are also reserved but invalid as form input.
reservedNames := []string{
"api",
"openapi3.v1.json",
"swagger.v1.json",
}
patternNotAllowedNames := []string{
"name.keys",
}
session := loginUser(t, "user2")
locale := translation.NewLocale("en-US")
check := func(name, msgKey string) {
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": name,
"email": "user2@example.com",
"language": "en-US",
})
resp := session.MakeRequest(t, req, http.StatusBadRequest)
data := DecodeJSON(t, resp, map[string]any{})
assert.Equal(t, locale.TrString(msgKey, name), data["errorMessage"])
assert.Equal(t, []any{"Name"}, data["errorFields"])
unittest.AssertNotExistsBean(t, &user_model.User{Name: name})
}
for _, name := range reservedNames {
check(name, "user.form.name_reserved")
}
for _, name := range patternNotAllowedNames {
check(name, "user.form.name_pattern_not_allowed")
}
}
func testChangeFullName(t *testing.T) {
session := loginUser(t, "user2")
postSettings := func(t *testing.T, values map[string]string, expectedStatus int) map[string]any {
t.Helper()
resp := session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user/settings", values), expectedStatus)
return DecodeJSON(t, resp, map[string]any{})
}
t.Run("Change", func(t *testing.T) {
postSettings(t, map[string]string{"full_name": "New Name"}, http.StatusOK)
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2", FullName: "New Name"})
})
t.Run("Disabled", func(t *testing.T) {
defer test.MockVariableValue(&setting.Admin.UserDisabledFeatures, container.SetOf(setting.UserFeatureChangeFullName))()
postSettings(t, map[string]string{"location": "Elsewhere"}, http.StatusOK)
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2", FullName: "New Name", Location: "Elsewhere"})
data := postSettings(t, map[string]string{"full_name": "Other Name"}, http.StatusBadRequest)
assert.Equal(t, []any{"FullName"}, data["errorFields"])
assert.Equal(t, "Changing full name is disabled.", data["errorMessage"])
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2", FullName: "New Name"})
})
t.Run("Clear", func(t *testing.T) {
postSettings(t, map[string]string{"full_name": ""}, http.StatusOK)
assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}).FullName)
})
}
func testRenameUsername(t *testing.T) {
session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": "newUsername",
"email": "user2@example.com",
"language": "en-US",
})
resp := session.MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "/user/settings", DecodeJSON(t, resp, map[string]any{})["redirect"])
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "newUsername"})
unittest.AssertNotExistsBean(t, &user_model.User{Name: "user2"})
}
func testUserLocationMapLink(t *testing.T) {
defer test.MockVariableValue(&setting.Service.UserLocationMapURL, "https://example/foo/")()
session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": "user2",
"email": "user@example.com",
"language": "en-US",
"location": "A/b",
})
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/user2/")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
AssertHTMLElement(t, htmlDoc, `a[href="https://example/foo/A%2Fb"]`, true)
}
+1 -119
View File
@@ -5,7 +5,6 @@ package integration
import (
"net/http"
"strings"
"testing"
asymkey_model "gitea.dev/models/asymkey"
@@ -18,7 +17,6 @@ import (
"gitea.dev/modules/setting"
api "gitea.dev/modules/structs"
"gitea.dev/modules/test"
"gitea.dev/modules/translation"
"gitea.dev/tests"
"github.com/stretchr/testify/assert"
@@ -29,14 +27,10 @@ func TestUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("ViewUser", testViewUser)
t.Run("FollowUser", testUserFollow)
t.Run("RenameInvalidUsername", testRenameInvalidUsername)
t.Run("RenameReservedUsername", testRenameReservedUsername)
t.Run("ViewLimitedAndPrivateUserAndRename", testViewLimitedAndPrivateUserAndRename)
t.Run("ExportUserGPGKeys", testExportUserGPGKeys)
t.Run("GetUserRss", testGetUserRss)
t.Run("ListStopWatches", testUserListStopWatches)
t.Run("LocationMapLink", testUserLocationMapLink)
t.Run("RenameUsername", testRenameUsername)
}
func testViewUser(t *testing.T) {
@@ -71,19 +65,6 @@ ssh-rsa AAAA
`, resp.Body.String())
}
func testRenameUsername(t *testing.T) {
session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": "newUsername",
"email": "user2@example.com",
"language": "en-US",
})
session.MakeRequest(t, req, http.StatusSeeOther)
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "newUsername"})
unittest.AssertNotExistsBean(t, &user_model.User{Name: "user2"})
}
func testViewLimitedAndPrivateUserAndRename(t *testing.T) {
// user 22 is a limited visibility org
org22 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22})
@@ -146,7 +127,7 @@ func testViewLimitedAndPrivateUserAndRename(t *testing.T) {
"name": newName,
"visibility": "2", // private
})
session2.MakeRequest(t, req, http.StatusSeeOther)
session2.MakeRequest(t, req, http.StatusOK)
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: newName})
unittest.AssertNotExistsBean(t, &user_model.User{Name: oldName})
@@ -157,87 +138,6 @@ func testViewLimitedAndPrivateUserAndRename(t *testing.T) {
session.MakeRequest(t, req, http.StatusTemporaryRedirect) // login user2 can visit private visibility user via old name
}
func testRenameInvalidUsername(t *testing.T) {
invalidUsernames := []string{
"%2f*",
"%2f.",
"%2f..",
"%00",
"thisHas ASpace",
"p<A>tho>lo<gical",
".",
"..",
".well-known",
".abc",
"abc.",
"a..bc",
"a...bc",
"a.-bc",
"a._bc",
"a_-bc",
"a/bc",
"☁️",
"-",
"--diff",
"-im-here",
"a space",
}
session := loginUser(t, "user2")
for _, invalidUsername := range invalidUsernames {
t.Logf("Testing username %s", invalidUsername)
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": invalidUsername,
"email": "user2@example.com",
})
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
assert.Contains(t,
htmlDoc.doc.Find(".ui.negative.message").Text(),
translation.NewLocale("en-US").TrString("form.username_error", "Name"),
)
unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername})
}
}
func testRenameReservedUsername(t *testing.T) {
// ".", "..", ".well-known" are also reserved but invalid as form input.
reservedNames := []string{
"api",
"openapi3.v1.json",
"swagger.v1.json",
}
patternNotAllowedNames := []string{
"name.keys",
}
session := loginUser(t, "user2")
locale := translation.NewLocale("en-US")
check := func(name, msgKey string) {
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": name,
"email": "user2@example.com",
"language": "en-US",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
actualMsg := strings.TrimSpace(htmlDoc.doc.Find(".ui.negative.message").Text())
assert.Equal(t, locale.TrString(msgKey, name), actualMsg)
unittest.AssertNotExistsBean(t, &user_model.User{Name: name})
}
for _, name := range reservedNames {
check(name, "user.form.name_reserved")
}
for _, name := range patternNotAllowedNames {
check(name, "user.form.name_pattern_not_allowed")
}
}
func testExportUserGPGKeys(t *testing.T) {
testExportUserGPGKeys := func(t *testing.T, user, expected string) {
t.Helper()
@@ -370,24 +270,6 @@ func testUserListStopWatches(t *testing.T) {
}
}
func testUserLocationMapLink(t *testing.T) {
defer test.MockVariableValue(&setting.Service.UserLocationMapURL, "https://example/foo/")()
session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"name": "user2",
"email": "user@example.com",
"language": "en-US",
"location": "A/b",
})
session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", "/user2/")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
AssertHTMLElement(t, htmlDoc, `a[href="https://example/foo/A%2Fb"]`, true)
}
func testUserFollow(t *testing.T) {
bot := &user_model.User{
Name: "follow-bot",