feat: apply service.VALID_SITE_URL_SCHEMES to apply to repository and organization profiles (#12962)

Turns out this was a one-line fix for each affected field (change the binding from `ValidUrl` to `ValidSiteUrl`), but the tests are rather verbose. The tests are, however, each a simple flow of Create Thing > Try HTTP Website > Try Different Website (notice failure) > Try Different Website With New Config (notice success). I wrote this PR by adding failing tests first, then making the change, for each affected field.

Not sure if this should be "feat:" or "fix:" tbh. I figured "fix:" for this PR since IMO the expected behavior is for `VALID_SITE_URL_SCHEMES` to apply in each of these cases, not only for user profiles via the UI form. (Later changed to "feat:" at @limiting-factor's suggestion, based on the observation that this change extends documented behavior.)

This PR deals with the server-side validation only. #12991 covers client-side validation (deriving a `pattern` attribute from `VALID_SITE_URL_SCHEMES`, etc.)

Closes #5519

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12962
Reviewed-by: limiting-factor <limiting-factor@noreply.codeberg.org>
This commit is contained in:
AverageHelper
2026-06-08 15:17:51 +02:00
committed by 0ko
parent 743fa9d188
commit 0e283c5485
14 changed files with 454 additions and 8 deletions
+1 -1
View File
@@ -915,7 +915,7 @@ LEVEL = Info
;;
;; Minimum amount of time a user must exist before comments are kept when the user is deleted.
;USER_DELETE_WITH_COMMENTS_MAX_TIME = 0
;; Valid site url schemes for user profiles
;; Valid site url schemes for user, organization, or repository profiles
;VALID_SITE_URL_SCHEMES=http,https
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+1 -1
View File
@@ -37,7 +37,7 @@ type EditUserOption struct {
FullName *string `json:"full_name" binding:"MaxSize(100)"`
Password string `json:"password" binding:"MaxSize(255)"`
MustChangePassword *bool `json:"must_change_password"`
Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"`
Website *string `json:"website" binding:"OmitEmpty;ValidSiteUrl;MaxSize(255)"`
Location *string `json:"location" binding:"MaxSize(50)"`
Pronouns *string `json:"pronouns" binding:"MaxSize(50)"`
Description *string `json:"description" binding:"MaxSize(255)"`
+2 -2
View File
@@ -38,7 +38,7 @@ type CreateOrgOption struct {
FullName string `json:"full_name" binding:"MaxSize(100)"`
Email string `json:"email" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(255)"`
Website string `json:"website" binding:"ValidUrl;MaxSize(255)"`
Website string `json:"website" binding:"ValidSiteUrl;MaxSize(255)"`
Location string `json:"location" binding:"MaxSize(50)"`
// possible values are `public` (default), `limited` or `private`
// enum: ["public", "limited", "private"]
@@ -53,7 +53,7 @@ type EditOrgOption struct {
FullName string `json:"full_name" binding:"MaxSize(100)"`
Email *string `json:"email" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(255)"`
Website string `json:"website" binding:"ValidUrl;MaxSize(255)"`
Website string `json:"website" binding:"ValidSiteUrl;MaxSize(255)"`
Location string `json:"location" binding:"MaxSize(50)"`
// possible values are `public`, `limited` or `private`
// enum: ["public", "limited", "private"]
+1 -1
View File
@@ -92,7 +92,7 @@ type UserSettings struct {
// swagger:model
type UserSettingsOptions struct {
FullName *string `json:"full_name" binding:"MaxSize(100)"`
Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"`
Website *string `json:"website" binding:"OmitEmpty;ValidSiteUrl;MaxSize(255)"`
Description *string `json:"description" binding:"MaxSize(255)"`
Location *string `json:"location" binding:"MaxSize(50)"`
Pronouns *string `json:"pronouns" binding:"MaxSize(50)"`
+1 -1
View File
@@ -39,7 +39,7 @@ type AdminEditUserForm struct {
FullName string `binding:"MaxSize(100)"`
Email string `binding:"Required;EmailForAdmin;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Website string `binding:"ValidSiteUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
Language string `binding:"MaxSize(5)"`
Pronouns string `binding:"MaxSize(50)"`
+1 -1
View File
@@ -40,7 +40,7 @@ type UpdateOrgSettingForm struct {
FullName string `binding:"MaxSize(100)"`
Email string `binding:"MaxSize(255)"`
Description string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Website string `binding:"ValidSiteUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
Visibility structs.VisibleType
MaxRepoCreation int
+1 -1
View File
@@ -130,7 +130,7 @@ func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, err
type RepoSettingForm struct {
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
Description string `binding:"MaxSize(2048)"`
Website string `binding:"ValidUrl;MaxSize(1024)"`
Website string `binding:"ValidSiteUrl;MaxSize(1024)"`
FollowingRepos string
Interval string
MirrorAddress string
+61
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"testing"
"time"
@@ -15,9 +16,12 @@ import (
issues_model "forgejo.org/models/issues"
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
"forgejo.org/modules/setting"
api "forgejo.org/modules/structs"
"forgejo.org/modules/test"
"forgejo.org/modules/timeutil"
"forgejo.org/tests"
"forgejo.org/tests/forgery"
"github.com/stretchr/testify/assert"
)
@@ -128,6 +132,63 @@ func TestAdminEditUserHideEmail(t *testing.T) {
htmlDoc.AssertElement(t, `input[name="hide_email"][checked]`, true)
}
func TestAdminEditUserWebsite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
user := forgery.CreateUser(t, nil)
urlStr := fmt.Sprintf("/admin/users/%d/edit", user.ID)
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"user_name": user.Name,
"login_name": user.LoginName,
"login_type": "0-0",
"email": user.Email,
"website": "https://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should not work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"user_name": user.Name,
"login_name": user.LoginName,
"login_type": "0-0",
"email": user.Email,
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
flash := doc.Find("#flash-message").Text()
assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash))
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// changing website should work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"user_name": user.Name,
"login_name": user.LoginName,
"login_type": "0-0",
"email": user.Email,
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
}
func testSuccessfulEdit(t *testing.T, formData user_model.User) {
makeRequest(t, formData, http.StatusSeeOther)
}
+61
View File
@@ -16,7 +16,9 @@ import (
"forgejo.org/modules/json"
"forgejo.org/modules/setting"
api "forgejo.org/modules/structs"
"forgejo.org/modules/test"
"forgejo.org/tests"
"forgejo.org/tests/forgery"
"github.com/gobwas/glob"
"github.com/stretchr/testify/assert"
@@ -512,6 +514,65 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) {
MakeRequest(t, req, http.StatusOK)
}
func TestAPIUser_Website(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
user := forgery.CreateUser(t, nil)
urlStr := fmt.Sprintf("/api/v1/admin/users/%s", user.Name)
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should work
website := "https://codeberg.org"
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditUserOption{
Website: &website,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiUser api.User
DecodeJSON(t, resp, &apiUser)
assert.Equal(t, website, apiUser.Website)
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should not work
website := "h3://codeberg.org"
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditUserOption{
Website: &website,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusUnprocessableEntity)
var apiErr api.APIError
DecodeJSON(t, resp, &apiErr)
assert.Equal(t, "[Website]: Url", apiErr.Message)
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// changing website should work
website := "h3://codeberg.org"
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditUserOption{
Website: &website,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiUser api.User
DecodeJSON(t, resp, &apiUser)
assert.Equal(t, website, apiUser.Website)
})
}
func TestAPIAdminListUserEmails(t *testing.T) {
defer tests.PrepareTestEnv(t)()
+119
View File
@@ -100,6 +100,67 @@ func TestAPIOrgCreate(t *testing.T) {
assert.Equal(t, "user1", users[0].UserName)
}
func TestAPIOrgCreateWithWebsite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization)
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// setting website should work
org := api.CreateOrgOption{
UserName: "user1_org",
FullName: "User1's organization",
Website: "https://codeberg.org",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusCreated)
var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)
assert.Equal(t, org.Website, apiOrg.Website)
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// setting website should not work
org := api.CreateOrgOption{
UserName: "user1_org_2",
FullName: "User1's second organization",
Website: "h3://codeberg.org",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusUnprocessableEntity)
var apiErr api.APIError
DecodeJSON(t, resp, &apiErr)
assert.Equal(t, "[Website]: Url", apiErr.Message)
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// setting website should work
org := api.CreateOrgOption{
UserName: "user1_org_2",
FullName: "User1's second organization",
Website: "h3://codeberg.org",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusCreated)
var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)
assert.Equal(t, org.Website, apiOrg.Website)
})
}
func TestAPIOrgRename(t *testing.T) {
defer tests.PrepareTestEnv(t)()
token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization)
@@ -150,6 +211,64 @@ func TestAPIOrgEdit(t *testing.T) {
assert.Equal(t, org.Visibility, apiOrg.Visibility)
}
func TestAPIOrgEditWebsite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
const orgName = "org3"
urlStr := fmt.Sprintf("/api/v1/orgs/%s", orgName)
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should work
org := api.EditOrgOption{
Website: "https://codeberg.org",
}
req := NewRequestWithJSON(t, "PATCH", urlStr, &org).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)
assert.Equal(t, org.Website, apiOrg.Website)
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should not work
org := api.EditOrgOption{
Website: "h3://codeberg.org",
}
req := NewRequestWithJSON(t, "PATCH", urlStr, &org).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusUnprocessableEntity)
var apiErr api.APIError
DecodeJSON(t, resp, &apiErr)
assert.Equal(t, "[Website]: Url", apiErr.Message)
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// changing website should work
org := api.EditOrgOption{
Website: "h3://codeberg.org",
}
req := NewRequestWithJSON(t, "PATCH", urlStr, &org).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)
assert.Equal(t, org.Website, apiOrg.Website)
})
}
func TestAPIOrgEditBadVisibility(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
+51
View File
@@ -6,10 +6,13 @@ package integration
import (
"fmt"
"net/http"
"strings"
"testing"
auth_model "forgejo.org/models/auth"
"forgejo.org/modules/setting"
api "forgejo.org/modules/structs"
"forgejo.org/modules/test"
"forgejo.org/tests"
"github.com/stretchr/testify/assert"
@@ -94,3 +97,51 @@ func TestOrgSettingsChangeEmail(t *testing.T) {
assert.Empty(t, org.Email)
})
}
func TestOrgSettingsUpdateWebsite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
const orgName = "org3"
urlStr := fmt.Sprintf("/org/%s/settings", orgName)
session := loginUser(t, "user1")
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"name": orgName,
"website": "https://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should not work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"name": orgName,
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
flash := doc.Find("#flash-message").Text()
assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash))
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// changing website should work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"name": orgName,
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
}
+52
View File
@@ -6,6 +6,7 @@ package integration
import (
"fmt"
"net/http"
"strings"
"testing"
"forgejo.org/models/db"
@@ -16,6 +17,7 @@ import (
"forgejo.org/models/unittest"
"forgejo.org/modules/optional"
"forgejo.org/modules/setting"
"forgejo.org/modules/test"
app_context "forgejo.org/services/context"
repo_service "forgejo.org/services/repository"
user_service "forgejo.org/services/user"
@@ -36,6 +38,56 @@ func TestRepoSettingsUnits(t *testing.T) {
session.MakeRequest(t, req, http.StatusOK)
}
func TestRepoSettingsUpdateWebsite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := forgery.CreateRepository(t, nil, nil)
session := loginUser(t, repo.Owner.Name)
urlStr := fmt.Sprintf("%s/settings", repo.Link())
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"action": "update",
"repo_name": repo.Name,
"website": "https://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should not work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"action": "update",
"repo_name": repo.Name,
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
flash := doc.Find("#flash-message").Text()
assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash))
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// changing website should work
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"action": "update",
"repo_name": repo.Name,
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
}
func TestRepoSettingsAdminOptions(t *testing.T) {
defer tests.PrepareTestEnv(t)()
+43
View File
@@ -5,6 +5,7 @@ package integration
import (
"net/http"
"strings"
"testing"
"forgejo.org/models/auth"
@@ -133,6 +134,48 @@ func TestUserSettingsDelete(t *testing.T) {
})
}
func TestUserSettingsUpdateWebsite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should work
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"website": "https://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should not work
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
flash := doc.Find("#flash-message").Text()
assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash))
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// changing website should work
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"website": "h3://codeberg.org",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
assertHasFlashMessages(t, resp, "success")
})
}
func TestUserRename(t *testing.T) {
defer unittest.OverrideFixtures("tests/integration/fixtures/TestUserRename")()
defer tests.PrepareTestEnv(t)()
+59
View File
@@ -742,6 +742,65 @@ func TestUserPronouns(t *testing.T) {
})
}
func TestUserEditWebsite(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := forgery.CreateUser(t, nil)
urlStr := "/api/v1/user/settings"
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
t.Run("an HTTPS website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should work
website := "https://codeberg.org"
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.UserSettingsOptions{
Website: &website,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiUser api.UserSettings
DecodeJSON(t, resp, &apiUser)
assert.Equal(t, website, apiUser.Website)
})
t.Run("an H3 website under default schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// changing website should not work
website := "h3://codeberg.org"
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.UserSettingsOptions{
Website: &website,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusUnprocessableEntity)
var apiErr api.APIError
DecodeJSON(t, resp, &apiErr)
assert.Equal(t, "[Website]: Url", apiErr.Message)
})
t.Run("an H3 website under custom schemes", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)()
setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3")
// changing website should work
website := "h3://codeberg.org"
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.UserSettingsOptions{
Website: &website,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiUser api.UserSettings
DecodeJSON(t, resp, &apiUser)
assert.Equal(t, website, apiUser.Website)
})
}
func TestUserTOTPMail(t *testing.T) {
defer tests.PrepareTestEnv(t)()