mirror of
https://github.com/go-gitea/gitea.git
synced 2026-03-28 03:12:08 +00:00
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:
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
2
templates/swagger/v1_json.tmpl
generated
2
templates/swagger/v1_json.tmpl
generated
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user