From e24c3f7a40dade43ae7d9e3354491184ad64c141 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 25 Mar 2026 08:23:11 +0100 Subject: [PATCH] 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 --- modules/structs/org.go | 16 +++--- routers/api/v1/org/org.go | 20 ++++--- routers/web/org/setting.go | 20 +++---- services/forms/org.go | 14 ++--- services/org/org.go | 17 ++++++ services/org/org_test.go | 54 ++++++++++++++----- services/user/email.go | 62 +++++++++++----------- services/user/email_test.go | 75 +++++++++++---------------- templates/swagger/v1_json.tmpl | 2 +- tests/integration/api_org_test.go | 55 ++++++++++++-------- tests/integration/integration_test.go | 3 +- tests/integration/org_test.go | 58 ++++++++++++++------- 12 files changed, 232 insertions(+), 164 deletions(-) diff --git a/modules/structs/org.go b/modules/structs/org.go index c3d70ebf00..d79b1d1d1c 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -66,23 +66,21 @@ type CreateOrgOption struct { 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 type EditOrgOption struct { // The full display name of the organization - FullName string `json:"full_name" binding:"MaxSize(100)"` - // The email address of the organization - Email string `json:"email" binding:"MaxSize(255)"` + FullName *string `json:"full_name" binding:"MaxSize(100)"` + // The email address of the organization; use empty string to clear + Email *string `json:"email" binding:"MaxSize(255)"` // 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 - Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` + Website *string `json:"website" binding:"ValidUrl;MaxSize(255)"` // 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` // 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 RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"` } diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index d42241f054..ce2a2e5580 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -5,6 +5,7 @@ package org import ( + "errors" "net/http" activities_model "code.gitea.io/gitea/models/activities" @@ -14,6 +15,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/optional" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/utils" @@ -379,19 +381,21 @@ func Edit(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.EditOrgOption) - if form.Email != "" { - if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Org.Organization.AsUser(), form.Email); err != nil { - ctx.APIErrorInternal(err) + if err := org.UpdateOrgEmailAddress(ctx, ctx.Org.Organization, form.Email); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.APIError(http.StatusUnprocessableEntity, err) return } + ctx.APIErrorInternal(err) + return } opts := &user_service.UpdateOptions{ - FullName: optional.Some(form.FullName), - Description: optional.Some(form.Description), - Website: optional.Some(form.Website), - Location: optional.Some(form.Location), - Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility), + FullName: optional.FromPtr(form.FullName), + Description: optional.FromPtr(form.Description), + Website: optional.FromPtr(form.Website), + Location: optional.FromPtr(form.Location), + Visibility: optional.FromMapLookup(api.VisibilityModes, optional.FromPtr(form.Visibility).Value()), RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess), } if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil { diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 04baa58b73..ca1da6617e 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -5,6 +5,7 @@ package org import ( + "errors" "net/http" "net/url" @@ -69,24 +70,25 @@ func SettingsPost(ctx *context.Context) { } org := ctx.Org.Organization - - if form.Email != "" { - if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil { + if err := org_service.UpdateOrgEmailAddress(ctx, org, form.Email); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { ctx.Data["Err_Email"] = true ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tplSettingsOptions, &form) return } + ctx.ServerError("UpdateOrgEmailAddress", err) + return } opts := &user_service.UpdateOptions{ - FullName: optional.Some(form.FullName), - Description: optional.Some(form.Description), - Website: optional.Some(form.Website), - Location: optional.Some(form.Location), - RepoAdminChangeTeamAccess: optional.Some(form.RepoAdminChangeTeamAccess), + FullName: optional.FromPtr(form.FullName), + Description: optional.FromPtr(form.Description), + Website: optional.FromPtr(form.Website), + Location: optional.FromPtr(form.Location), + RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess), } 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 { diff --git a/services/forms/org.go b/services/forms/org.go index 3997e1da84..8a8106e8cf 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -36,13 +36,13 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding // UpdateOrgSettingForm form for updating organization settings type UpdateOrgSettingForm struct { - FullName string `binding:"MaxSize(100)"` - Email string `binding:"MaxSize(255)"` - Description string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` - Location string `binding:"MaxSize(50)"` - MaxRepoCreation int - RepoAdminChangeTeamAccess bool + FullName *string `binding:"MaxSize(100)"` + Email *string `binding:"MaxSize(255)"` + Description *string `binding:"MaxSize(255)"` + Website *string `binding:"ValidUrl;MaxSize(255)"` + Location *string `binding:"MaxSize(50)"` + MaxRepoCreation *int + RepoAdminChangeTeamAccess *bool } // Validate validates the fields diff --git a/services/org/org.go b/services/org/org.go index 8da77c691c..32c46d7cb9 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -168,3 +168,20 @@ func ChangeOrganizationVisibility(ctx context.Context, org *org_model.Organizati 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") +} diff --git a/services/org/org_test.go b/services/org/org_test.go index 5fdc1a6fd5..5253c73902 100644 --- a/services/org/org_test.go +++ b/services/org/org_test.go @@ -10,28 +10,54 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { unittest.MainTest(m) } -func TestDeleteOrganization(t *testing.T) { - assert.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}) +func TestOrg(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) - 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)) + t.Run("UpdateOrgEmailAddress", func(t *testing.T) { + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) + originalEmail := org.Email - user := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 5}) - assert.Error(t, DeleteOrganization(t.Context(), user, false)) - unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) + require.NoError(t, UpdateOrgEmailAddress(t.Context(), org, nil)) + unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3, Email: originalEmail}) + + 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{}) + }) } diff --git a/services/user/email.go b/services/user/email.go index c45b3b3ec9..927e4b7234 100644 --- a/services/user/email.go +++ b/services/user/email.go @@ -14,7 +14,14 @@ import ( "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 { + // 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) { 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 { - if !u.IsOrganization() { - // Check if address exists already - email, err := user_model.GetEmailAddressByEmail(ctx, emailStr) - if err != nil && !errors.Is(err, util.ErrNotExist) { - return err - } - if email != nil { - if email.IsPrimary && email.UID == u.ID { - return nil - } - return user_model.ErrEmailAlreadyUsed{Email: emailStr} + // Check if address exists already + email, err := user_model.GetEmailAddressByEmail(ctx, emailStr) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return err + } + if email != nil { + if email.IsPrimary && email.UID == u.ID { + return nil } + return user_model.ErrEmailAlreadyUsed{Email: emailStr} + } - // Remove old primary address - primary, err := user_model.GetPrimaryEmailAddressOfUser(ctx, u.ID) - if err != nil { - return err - } - if _, err := db.DeleteByID[user_model.EmailAddress](ctx, primary.ID); err != nil { - return err - } + // Remove old primary address + primary, err := user_model.GetPrimaryEmailAddressOfUser(ctx, u.ID) + if err != nil { + return err + } + if _, err := db.DeleteByID[user_model.EmailAddress](ctx, primary.ID); err != nil { + return err + } - // Insert new primary address - if _, err := user_model.InsertEmailAddress(ctx, &user_model.EmailAddress{ - UID: u.ID, - Email: emailStr, - IsActivated: true, - IsPrimary: true, - }); err != nil { - return err - } + // Insert new primary address + if _, err := user_model.InsertEmailAddress(ctx, &user_model.EmailAddress{ + UID: u.ID, + Email: emailStr, + IsActivated: true, + IsPrimary: true, + }); err != nil { + return err } u.Email = emailStr - return user_model.UpdateUserCols(ctx, u, "email") }) } diff --git a/services/user/email_test.go b/services/user/email_test.go index a031b12cad..9fb0560b05 100644 --- a/services/user/email_test.go +++ b/services/user/email_test.go @@ -6,17 +6,16 @@ package user import ( "testing" - organization_model "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "github.com/stretchr/testify/assert" ) -func TestReplacePrimaryEmailAddress(t *testing.T) { +func TestUserEmail(t *testing.T) { 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}) 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")) }) - t.Run("Organization", func(t *testing.T) { - org := unittest.AssertExistsAndLoadBean(t, &organization_model.Organization{ID: 3}) + t.Run("AddEmailAddresses", func(t *testing.T) { + 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)) -} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 20db48b91a..adc6c18175 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -24855,7 +24855,7 @@ "x-go-name": "Description" }, "email": { - "description": "The email address of the organization", + "description": "The email address of the organization; use empty string to clear", "type": "string", "x-go-name": "Email" }, diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 6b7826fbb8..42f9e4cbf6 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -137,34 +137,45 @@ func TestAPIOrgGeneral(t *testing.T) { }) t.Run("OrgEdit", func(t *testing.T) { - org := api.EditOrgOption{ - FullName: "Org3 organization new full name", - 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) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) + assert.NotEqual(t, api.VisibleTypeLimited, org3.Visibility) - var apiOrg api.Organization - DecodeJSON(t, resp, &apiOrg) + org3Edit := api.EditOrgOption{ + 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, org.FullName, apiOrg.FullName) - assert.Equal(t, org.Description, apiOrg.Description) - assert.Equal(t, org.Website, apiOrg.Website) - assert.Equal(t, org.Location, apiOrg.Location) - assert.Equal(t, org.Visibility, apiOrg.Visibility) + assert.Equal(t, *org3Edit.FullName, apiOrg.FullName) + assert.Equal(t, *org3Edit.Description, apiOrg.Description) + assert.Equal(t, *org3Edit.Website, apiOrg.Website) + assert.Equal(t, *org3Edit.Location, apiOrg.Location) + 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{ - FullName: "Org3 organization new full name", - Description: "A new description", - Website: "https://try.gitea.io/new", - Location: "Beijing", - Visibility: "badvisibility", + Visibility: new("invalid-visibility"), } req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token) MakeRequest(t, req, http.StatusUnprocessableEntity) diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index a4807814df..e8b0cbd641 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -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() // 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) require.NoError(t, decoder.Decode(v)) + return v } func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) { diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index 3ed7baa5ba..cedc0406ca 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -23,9 +23,18 @@ import ( "github.com/stretchr/testify/require" ) -func TestOrgRepos(t *testing.T) { +func TestOrg(t *testing.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 ( users = []string{"user1", "user2"} cases = map[string][]string{ @@ -53,10 +62,8 @@ func TestOrgRepos(t *testing.T) { } } -func TestLimitedOrg(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // not logged in user +func testLimitedOrg(t *testing.T) { + // not logged-in user req := NewRequest(t, "GET", "/limited_org") MakeRequest(t, req, http.StatusNotFound) 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) } -func TestPrivateOrg(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // not logged in user +func testPrivateOrg(t *testing.T) { + // not logged-in user req := NewRequest(t, "GET", "/privated_org") MakeRequest(t, req, http.StatusNotFound) 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) } -func TestOrgMembers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // not logged in user +func testOrgMembers(t *testing.T) { + // not logged-in user req := NewRequest(t, "GET", "/org/org25/members") MakeRequest(t, req, http.StatusOK) @@ -140,9 +143,7 @@ func TestOrgMembers(t *testing.T) { session.MakeRequest(t, req, http.StatusOK) } -func TestOrgRestrictedUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - +func testOrgRestrictedUser(t *testing.T) { // privated_org is a private org who has id 23 orgName := "privated_org" @@ -200,9 +201,7 @@ func TestOrgRestrictedUser(t *testing.T) { restrictedSession.MakeRequest(t, req, http.StatusOK) } -func TestTeamSearch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - +func testTeamSearch(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) 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" }) } + +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) +}