Fix org contact email not clearable once set (#36975)

When the email field was submitted as empty in org settings (web and
API), the previous guard `if form.Email != ""` silently skipped the
update, making it impossible to remove a contact email after it was set.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Nicolas
2026-03-25 08:23:11 +01:00
committed by GitHub
parent 943ff75233
commit e24c3f7a40
12 changed files with 232 additions and 164 deletions

View File

@@ -66,23 +66,21 @@ type CreateOrgOption struct {
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
} }
// TODO: make EditOrgOption fields optional after https://gitea.com/go-chi/binding/pulls/5 got merged
// EditOrgOption options for editing an organization // EditOrgOption options for editing an organization
type EditOrgOption struct { type EditOrgOption struct {
// The full display name of the organization // The full display name of the organization
FullName string `json:"full_name" binding:"MaxSize(100)"` FullName *string `json:"full_name" binding:"MaxSize(100)"`
// The email address of the organization // The email address of the organization; use empty string to clear
Email string `json:"email" binding:"MaxSize(255)"` Email *string `json:"email" binding:"MaxSize(255)"`
// The description of the organization // The description of the organization
Description string `json:"description" binding:"MaxSize(255)"` Description *string `json:"description" binding:"MaxSize(255)"`
// The website URL of the organization // The website URL of the organization
Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` Website *string `json:"website" binding:"ValidUrl;MaxSize(255)"`
// The location of the organization // The location of the organization
Location string `json:"location" binding:"MaxSize(50)"` Location *string `json:"location" binding:"MaxSize(50)"`
// possible values are `public`, `limited` or `private` // possible values are `public`, `limited` or `private`
// enum: public,limited,private // enum: public,limited,private
Visibility string `json:"visibility" binding:"In(,public,limited,private)"` Visibility *string `json:"visibility" binding:"In(,public,limited,private)"`
// Whether repository administrators can change team access // Whether repository administrators can change team access
RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"` RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"`
} }

View File

@@ -5,6 +5,7 @@
package org package org
import ( import (
"errors"
"net/http" "net/http"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
@@ -14,6 +15,7 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
@@ -379,19 +381,21 @@ func Edit(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditOrgOption) form := web.GetForm(ctx).(*api.EditOrgOption)
if form.Email != "" { if err := org.UpdateOrgEmailAddress(ctx, ctx.Org.Organization, form.Email); err != nil {
if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Org.Organization.AsUser(), form.Email); err != nil { if errors.Is(err, util.ErrInvalidArgument) {
ctx.APIErrorInternal(err) ctx.APIError(http.StatusUnprocessableEntity, err)
return return
} }
ctx.APIErrorInternal(err)
return
} }
opts := &user_service.UpdateOptions{ opts := &user_service.UpdateOptions{
FullName: optional.Some(form.FullName), FullName: optional.FromPtr(form.FullName),
Description: optional.Some(form.Description), Description: optional.FromPtr(form.Description),
Website: optional.Some(form.Website), Website: optional.FromPtr(form.Website),
Location: optional.Some(form.Location), Location: optional.FromPtr(form.Location),
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility), Visibility: optional.FromMapLookup(api.VisibilityModes, optional.FromPtr(form.Visibility).Value()),
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess), RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
} }
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil { if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {

View File

@@ -5,6 +5,7 @@
package org package org
import ( import (
"errors"
"net/http" "net/http"
"net/url" "net/url"
@@ -69,24 +70,25 @@ func SettingsPost(ctx *context.Context) {
} }
org := ctx.Org.Organization org := ctx.Org.Organization
if err := org_service.UpdateOrgEmailAddress(ctx, org, form.Email); err != nil {
if form.Email != "" { if errors.Is(err, util.ErrInvalidArgument) {
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
ctx.Data["Err_Email"] = true ctx.Data["Err_Email"] = true
ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tplSettingsOptions, &form) ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tplSettingsOptions, &form)
return return
} }
ctx.ServerError("UpdateOrgEmailAddress", err)
return
} }
opts := &user_service.UpdateOptions{ opts := &user_service.UpdateOptions{
FullName: optional.Some(form.FullName), FullName: optional.FromPtr(form.FullName),
Description: optional.Some(form.Description), Description: optional.FromPtr(form.Description),
Website: optional.Some(form.Website), Website: optional.FromPtr(form.Website),
Location: optional.Some(form.Location), Location: optional.FromPtr(form.Location),
RepoAdminChangeTeamAccess: optional.Some(form.RepoAdminChangeTeamAccess), RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
} }
if ctx.Doer.IsAdmin { if ctx.Doer.IsAdmin {
opts.MaxRepoCreation = optional.Some(form.MaxRepoCreation) opts.MaxRepoCreation = optional.FromPtr(form.MaxRepoCreation)
} }
if err := user_service.UpdateUser(ctx, org.AsUser(), opts); err != nil { if err := user_service.UpdateUser(ctx, org.AsUser(), opts); err != nil {

View File

@@ -36,13 +36,13 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding
// UpdateOrgSettingForm form for updating organization settings // UpdateOrgSettingForm form for updating organization settings
type UpdateOrgSettingForm struct { type UpdateOrgSettingForm struct {
FullName string `binding:"MaxSize(100)"` FullName *string `binding:"MaxSize(100)"`
Email string `binding:"MaxSize(255)"` Email *string `binding:"MaxSize(255)"`
Description string `binding:"MaxSize(255)"` Description *string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"` Website *string `binding:"ValidUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"` Location *string `binding:"MaxSize(50)"`
MaxRepoCreation int MaxRepoCreation *int
RepoAdminChangeTeamAccess bool RepoAdminChangeTeamAccess *bool
} }
// Validate validates the fields // Validate validates the fields

View File

@@ -168,3 +168,20 @@ func ChangeOrganizationVisibility(ctx context.Context, org *org_model.Organizati
return nil return nil
}) })
} }
// UpdateOrgEmailAddress validates and updates the organization's contact email.
// A nil email means no change.
func UpdateOrgEmailAddress(ctx context.Context, org *org_model.Organization, email *string) error {
if email == nil {
return nil
}
if *email != "" {
if err := user_model.ValidateEmail(*email); err != nil {
return err
}
}
org.Email = *email
return user_model.UpdateUserCols(ctx, org.AsUser(), "email")
}

View File

@@ -10,28 +10,54 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
unittest.MainTest(m) unittest.MainTest(m)
} }
func TestDeleteOrganization(t *testing.T) { func TestOrg(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) require.NoError(t, unittest.PrepareTestDatabase())
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6})
assert.NoError(t, DeleteOrganization(t.Context(), org, false))
unittest.AssertNotExistsBean(t, &organization.Organization{ID: 6})
unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: 6})
unittest.AssertNotExistsBean(t, &organization.Team{OrgID: 6})
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) t.Run("UpdateOrgEmailAddress", func(t *testing.T) {
err := DeleteOrganization(t.Context(), org, false) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
assert.Error(t, err) originalEmail := org.Email
assert.True(t, repo_model.IsErrUserOwnRepos(err))
user := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 5}) require.NoError(t, UpdateOrgEmailAddress(t.Context(), org, nil))
assert.Error(t, DeleteOrganization(t.Context(), user, false)) unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3, Email: originalEmail})
unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
newEmail := "contact@org3.example.com"
require.NoError(t, UpdateOrgEmailAddress(t.Context(), org, &newEmail))
unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3, Email: newEmail})
invalidEmail := "invalid email"
err := UpdateOrgEmailAddress(t.Context(), org, &invalidEmail)
require.ErrorIs(t, err, util.ErrInvalidArgument)
unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3, Email: newEmail})
require.NoError(t, UpdateOrgEmailAddress(t.Context(), org, new("")))
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3, Email: ""})
assert.Empty(t, org.Email)
})
t.Run("DeleteOrganization", func(t *testing.T) {
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6})
assert.NoError(t, DeleteOrganization(t.Context(), org, false))
unittest.AssertNotExistsBean(t, &organization.Organization{ID: 6})
unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: 6})
unittest.AssertNotExistsBean(t, &organization.Team{OrgID: 6})
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
err := DeleteOrganization(t.Context(), org, false)
assert.Error(t, err)
assert.True(t, repo_model.IsErrUserOwnRepos(err))
user := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 5})
assert.Error(t, DeleteOrganization(t.Context(), user, false))
unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
})
} }

View File

@@ -14,7 +14,14 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
// ReplacePrimaryEmailAddress replaces the user's primary email address with the given email address.
// It also updates the user's email field to match the new primary email address.
func ReplacePrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error { func ReplacePrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error {
// FIXME: this check is from old logic, but it is not right, there are far more user types, not only "organization"
if u.IsOrganization() {
return util.NewInvalidArgumentErrorf("user %s is an organization", u.Name)
}
if strings.EqualFold(u.Email, emailStr) { if strings.EqualFold(u.Email, emailStr) {
return nil return nil
} }
@@ -24,41 +31,38 @@ func ReplacePrimaryEmailAddress(ctx context.Context, u *user_model.User, emailSt
} }
return db.WithTx(ctx, func(ctx context.Context) error { return db.WithTx(ctx, func(ctx context.Context) error {
if !u.IsOrganization() { // Check if address exists already
// Check if address exists already email, err := user_model.GetEmailAddressByEmail(ctx, emailStr)
email, err := user_model.GetEmailAddressByEmail(ctx, emailStr) if err != nil && !errors.Is(err, util.ErrNotExist) {
if err != nil && !errors.Is(err, util.ErrNotExist) { return err
return err }
} if email != nil {
if email != nil { if email.IsPrimary && email.UID == u.ID {
if email.IsPrimary && email.UID == u.ID { return nil
return nil
}
return user_model.ErrEmailAlreadyUsed{Email: emailStr}
} }
return user_model.ErrEmailAlreadyUsed{Email: emailStr}
}
// Remove old primary address // Remove old primary address
primary, err := user_model.GetPrimaryEmailAddressOfUser(ctx, u.ID) primary, err := user_model.GetPrimaryEmailAddressOfUser(ctx, u.ID)
if err != nil { if err != nil {
return err return err
} }
if _, err := db.DeleteByID[user_model.EmailAddress](ctx, primary.ID); err != nil { if _, err := db.DeleteByID[user_model.EmailAddress](ctx, primary.ID); err != nil {
return err return err
} }
// Insert new primary address // Insert new primary address
if _, err := user_model.InsertEmailAddress(ctx, &user_model.EmailAddress{ if _, err := user_model.InsertEmailAddress(ctx, &user_model.EmailAddress{
UID: u.ID, UID: u.ID,
Email: emailStr, Email: emailStr,
IsActivated: true, IsActivated: true,
IsPrimary: true, IsPrimary: true,
}); err != nil { }); err != nil {
return err return err
}
} }
u.Email = emailStr u.Email = emailStr
return user_model.UpdateUserCols(ctx, u, "email") return user_model.UpdateUserCols(ctx, u, "email")
}) })
} }

View File

@@ -6,17 +6,16 @@ package user
import ( import (
"testing" "testing"
organization_model "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestReplacePrimaryEmailAddress(t *testing.T) { func TestUserEmail(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
t.Run("User", func(t *testing.T) { t.Run("PrimaryEmailAddress", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 13}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 13})
emails, err := user_model.GetEmailAddresses(t.Context(), user.ID) emails, err := user_model.GetEmailAddresses(t.Context(), user.ID)
@@ -42,50 +41,36 @@ func TestReplacePrimaryEmailAddress(t *testing.T) {
assert.NoError(t, ReplacePrimaryEmailAddress(t.Context(), user, "primary-13@example.com")) assert.NoError(t, ReplacePrimaryEmailAddress(t.Context(), user, "primary-13@example.com"))
}) })
t.Run("Organization", func(t *testing.T) { t.Run("AddEmailAddresses", func(t *testing.T) {
org := unittest.AssertExistsAndLoadBean(t, &organization_model.Organization{ID: 3}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
assert.Equal(t, "org3@example.com", org.Email) assert.Error(t, AddEmailAddresses(t.Context(), user, []string{" invalid email "}))
assert.NoError(t, ReplacePrimaryEmailAddress(t.Context(), org.AsUser(), "primary-org@example.com")) emails := []string{"user1234@example.com", "user5678@example.com"}
assert.Equal(t, "primary-org@example.com", org.Email) assert.NoError(t, AddEmailAddresses(t.Context(), user, emails))
err := AddEmailAddresses(t.Context(), user, emails)
assert.Error(t, err)
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
})
t.Run("DeleteEmailAddresses", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
emails := []string{"user2-2@example.com"}
err := DeleteEmailAddresses(t.Context(), user, emails)
assert.NoError(t, err)
err = DeleteEmailAddresses(t.Context(), user, emails)
assert.Error(t, err)
assert.True(t, user_model.IsErrEmailAddressNotExist(err))
emails = []string{"user2@example.com"}
err = DeleteEmailAddresses(t.Context(), user, emails)
assert.Error(t, err)
assert.True(t, user_model.IsErrPrimaryEmailCannotDelete(err))
}) })
} }
func TestAddEmailAddresses(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
assert.Error(t, AddEmailAddresses(t.Context(), user, []string{" invalid email "}))
emails := []string{"user1234@example.com", "user5678@example.com"}
assert.NoError(t, AddEmailAddresses(t.Context(), user, emails))
err := AddEmailAddresses(t.Context(), user, emails)
assert.Error(t, err)
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
}
func TestDeleteEmailAddresses(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
emails := []string{"user2-2@example.com"}
err := DeleteEmailAddresses(t.Context(), user, emails)
assert.NoError(t, err)
err = DeleteEmailAddresses(t.Context(), user, emails)
assert.Error(t, err)
assert.True(t, user_model.IsErrEmailAddressNotExist(err))
emails = []string{"user2@example.com"}
err = DeleteEmailAddresses(t.Context(), user, emails)
assert.Error(t, err)
assert.True(t, user_model.IsErrPrimaryEmailCannotDelete(err))
}

View File

@@ -24855,7 +24855,7 @@
"x-go-name": "Description" "x-go-name": "Description"
}, },
"email": { "email": {
"description": "The email address of the organization", "description": "The email address of the organization; use empty string to clear",
"type": "string", "type": "string",
"x-go-name": "Email" "x-go-name": "Email"
}, },

View File

@@ -137,34 +137,45 @@ func TestAPIOrgGeneral(t *testing.T) {
}) })
t.Run("OrgEdit", func(t *testing.T) { t.Run("OrgEdit", func(t *testing.T) {
org := api.EditOrgOption{ org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
FullName: "Org3 organization new full name", assert.NotEqual(t, api.VisibleTypeLimited, org3.Visibility)
Description: "A new description",
Website: "https://try.gitea.io/new",
Location: "Beijing",
Visibility: "private",
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token)
resp := MakeRequest(t, req, http.StatusOK)
var apiOrg api.Organization org3Edit := api.EditOrgOption{
DecodeJSON(t, resp, &apiOrg) FullName: new("new full name"),
Description: new("new description"),
Website: new("https://org3-new-website.example.com"),
Location: new("new location"),
Visibility: new("limited"),
Email: new("org3-new-email@example.com"),
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org3Edit).AddTokenAuth(user1Token)
resp := MakeRequest(t, req, http.StatusOK)
apiOrg := DecodeJSON(t, resp, &api.Organization{})
assert.Equal(t, "org3", apiOrg.Name) assert.Equal(t, "org3", apiOrg.Name)
assert.Equal(t, org.FullName, apiOrg.FullName) assert.Equal(t, *org3Edit.FullName, apiOrg.FullName)
assert.Equal(t, org.Description, apiOrg.Description) assert.Equal(t, *org3Edit.Description, apiOrg.Description)
assert.Equal(t, org.Website, apiOrg.Website) assert.Equal(t, *org3Edit.Website, apiOrg.Website)
assert.Equal(t, org.Location, apiOrg.Location) assert.Equal(t, *org3Edit.Location, apiOrg.Location)
assert.Equal(t, org.Visibility, apiOrg.Visibility) assert.Equal(t, *org3Edit.Visibility, apiOrg.Visibility)
assert.Equal(t, *org3Edit.Email, apiOrg.Email)
org3 = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
assert.Equal(t, api.VisibleTypeLimited, org3.Visibility)
// empty email can clear the email, nil fields won't change the settings
req = NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &api.EditOrgOption{
Email: new(""),
}).AddTokenAuth(user1Token)
resp = MakeRequest(t, req, http.StatusOK)
apiOrg = DecodeJSON(t, resp, &api.Organization{})
assert.Equal(t, *org3Edit.FullName, apiOrg.FullName)
assert.Equal(t, *org3Edit.Visibility, apiOrg.Visibility)
assert.Empty(t, apiOrg.Email)
}) })
t.Run("OrgEditBadVisibility", func(t *testing.T) { t.Run("OrgEditInvalidVisibility", func(t *testing.T) {
org := api.EditOrgOption{ org := api.EditOrgOption{
FullName: "Org3 organization new full name", Visibility: new("invalid-visibility"),
Description: "A new description",
Website: "https://try.gitea.io/new",
Location: "Beijing",
Visibility: "badvisibility",
} }
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token) req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token)
MakeRequest(t, req, http.StatusUnprocessableEntity) MakeRequest(t, req, http.StatusUnprocessableEntity)

View File

@@ -410,12 +410,13 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
} }
} }
func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v any) { func DecodeJSON[T any](t testing.TB, resp *httptest.ResponseRecorder, v T) (ret T) {
t.Helper() t.Helper()
// FIXME: JSON-KEY-CASE: for testing purpose only, because many structs don't provide `json` tags, they just use capitalized field names // FIXME: JSON-KEY-CASE: for testing purpose only, because many structs don't provide `json` tags, they just use capitalized field names
decoder := json.NewDecoderCaseInsensitive(resp.Body) decoder := json.NewDecoderCaseInsensitive(resp.Body)
require.NoError(t, decoder.Decode(v)) require.NoError(t, decoder.Decode(v))
return v
} }
func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) { func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) {

View File

@@ -23,9 +23,18 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestOrgRepos(t *testing.T) { func TestOrg(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
t.Run("OrgRepos", testOrgRepos)
t.Run("PrivateOrg", testPrivateOrg)
t.Run("LimitedOrg", testLimitedOrg)
t.Run("OrgMembers", testOrgMembers)
t.Run("OrgRestrictedUser", testOrgRestrictedUser)
t.Run("TeamSearch", testTeamSearch)
t.Run("OrgSettings", testOrgSettings)
}
func testOrgRepos(t *testing.T) {
var ( var (
users = []string{"user1", "user2"} users = []string{"user1", "user2"}
cases = map[string][]string{ cases = map[string][]string{
@@ -53,10 +62,8 @@ func TestOrgRepos(t *testing.T) {
} }
} }
func TestLimitedOrg(t *testing.T) { func testLimitedOrg(t *testing.T) {
defer tests.PrepareTestEnv(t)() // not logged-in user
// not logged in user
req := NewRequest(t, "GET", "/limited_org") req := NewRequest(t, "GET", "/limited_org")
MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org") req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org")
@@ -83,10 +90,8 @@ func TestLimitedOrg(t *testing.T) {
session.MakeRequest(t, req, http.StatusOK) session.MakeRequest(t, req, http.StatusOK)
} }
func TestPrivateOrg(t *testing.T) { func testPrivateOrg(t *testing.T) {
defer tests.PrepareTestEnv(t)() // not logged-in user
// not logged in user
req := NewRequest(t, "GET", "/privated_org") req := NewRequest(t, "GET", "/privated_org")
MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org") req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org")
@@ -122,10 +127,8 @@ func TestPrivateOrg(t *testing.T) {
session.MakeRequest(t, req, http.StatusOK) session.MakeRequest(t, req, http.StatusOK)
} }
func TestOrgMembers(t *testing.T) { func testOrgMembers(t *testing.T) {
defer tests.PrepareTestEnv(t)() // not logged-in user
// not logged in user
req := NewRequest(t, "GET", "/org/org25/members") req := NewRequest(t, "GET", "/org/org25/members")
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)
@@ -140,9 +143,7 @@ func TestOrgMembers(t *testing.T) {
session.MakeRequest(t, req, http.StatusOK) session.MakeRequest(t, req, http.StatusOK)
} }
func TestOrgRestrictedUser(t *testing.T) { func testOrgRestrictedUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// privated_org is a private org who has id 23 // privated_org is a private org who has id 23
orgName := "privated_org" orgName := "privated_org"
@@ -200,9 +201,7 @@ func TestOrgRestrictedUser(t *testing.T) {
restrictedSession.MakeRequest(t, req, http.StatusOK) restrictedSession.MakeRequest(t, req, http.StatusOK)
} }
func TestTeamSearch(t *testing.T) { func testTeamSearch(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
@@ -251,3 +250,24 @@ func TestTeamSearch(t *testing.T) {
assert.Len(t, teams, 1) // team permission is "write", so can write "code" assert.Len(t, teams, 1) // team permission is "write", so can write "code"
}) })
} }
func testOrgSettings(t *testing.T) {
session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/org/org3/settings", map[string]string{
"full_name": "org3 new full name",
"email": "org3-new-email@example.com",
})
session.MakeRequest(t, req, http.StatusSeeOther)
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
assert.Equal(t, "org3 new full name", org.FullName)
assert.Equal(t, "org3-new-email@example.com", org.Email)
req = NewRequestWithValues(t, "POST", "/org/org3/settings", map[string]string{
"email": "", // empty email means "clear email"
})
session.MakeRequest(t, req, http.StatusSeeOther)
org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
assert.Equal(t, "org3 new full name", org.FullName)
assert.Empty(t, org.Email)
}