* update and use labelColorPattern * add TestCases * fix lint * # optional for templates * fix typo * some more * fix lint of **master**tags/v1.10.5
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"strings" | |||||
"testing" | "testing" | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
) | ) | ||||
func TestAPIModifyLabels(t *testing.T) { | |||||
assert.NoError(t, models.LoadFixtures()) | |||||
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) | |||||
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) | |||||
session := loginUser(t, owner.Name) | |||||
token := getTokenForLoggedInUser(t, session) | |||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels?token=%s", owner.Name, repo.Name, token) | |||||
// CreateLabel | |||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ | |||||
Name: "TestL 1", | |||||
Color: "abcdef", | |||||
Description: "test label", | |||||
}) | |||||
resp := session.MakeRequest(t, req, http.StatusCreated) | |||||
apiLabel := new(api.Label) | |||||
DecodeJSON(t, resp, &apiLabel) | |||||
dbLabel := models.AssertExistsAndLoadBean(t, &models.Label{ID: apiLabel.ID, RepoID: repo.ID}).(*models.Label) | |||||
assert.EqualValues(t, dbLabel.Name, apiLabel.Name) | |||||
assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) | |||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ | |||||
Name: "TestL 2", | |||||
Color: "#123456", | |||||
Description: "jet another test label", | |||||
}) | |||||
session.MakeRequest(t, req, http.StatusCreated) | |||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ | |||||
Name: "WrongTestL", | |||||
Color: "#12345g", | |||||
}) | |||||
session.MakeRequest(t, req, http.StatusUnprocessableEntity) | |||||
//ListLabels | |||||
req = NewRequest(t, "GET", urlStr) | |||||
resp = session.MakeRequest(t, req, http.StatusOK) | |||||
var apiLabels []*api.Label | |||||
DecodeJSON(t, resp, &apiLabels) | |||||
assert.Len(t, apiLabels, 2) | |||||
//GetLabel | |||||
singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d?token=%s", owner.Name, repo.Name, dbLabel.ID, token) | |||||
req = NewRequest(t, "GET", singleURLStr) | |||||
resp = session.MakeRequest(t, req, http.StatusOK) | |||||
DecodeJSON(t, resp, &apiLabel) | |||||
assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) | |||||
//EditLabel | |||||
newName := "LabelNewName" | |||||
newColor := "09876a" | |||||
newColorWrong := "09g76a" | |||||
req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ | |||||
Name: &newName, | |||||
Color: &newColor, | |||||
}) | |||||
resp = session.MakeRequest(t, req, http.StatusOK) | |||||
DecodeJSON(t, resp, &apiLabel) | |||||
assert.EqualValues(t, newColor, apiLabel.Color) | |||||
req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ | |||||
Color: &newColorWrong, | |||||
}) | |||||
session.MakeRequest(t, req, http.StatusUnprocessableEntity) | |||||
//DeleteLabel | |||||
req = NewRequest(t, "DELETE", singleURLStr) | |||||
resp = session.MakeRequest(t, req, http.StatusNoContent) | |||||
} | |||||
func TestAPIAddIssueLabels(t *testing.T) { | func TestAPIAddIssueLabels(t *testing.T) { | ||||
assert.NoError(t, models.LoadFixtures()) | assert.NoError(t, models.LoadFixtures()) | ||||
"xorm.io/xorm" | "xorm.io/xorm" | ||||
) | ) | ||||
var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})") | |||||
// LabelColorPattern is a regexp witch can validate LabelColor | |||||
var LabelColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") | |||||
// Label represents a label of repository for issues. | |||||
type Label struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
RepoID int64 `xorm:"INDEX"` | |||||
Name string | |||||
Description string | |||||
Color string `xorm:"VARCHAR(7)"` | |||||
NumIssues int | |||||
NumClosedIssues int | |||||
NumOpenIssues int `xorm:"-"` | |||||
IsChecked bool `xorm:"-"` | |||||
QueryString string `xorm:"-"` | |||||
IsSelected bool `xorm:"-"` | |||||
IsExcluded bool `xorm:"-"` | |||||
} | |||||
// APIFormat converts a Label to the api.Label format | |||||
func (label *Label) APIFormat() *api.Label { | |||||
return &api.Label{ | |||||
ID: label.ID, | |||||
Name: label.Name, | |||||
Color: strings.TrimLeft(label.Color, "#"), | |||||
Description: label.Description, | |||||
} | |||||
} | |||||
// GetLabelTemplateFile loads the label template file by given name, | // GetLabelTemplateFile loads the label template file by given name, | ||||
// then parses and returns a list of name-color pairs and optionally description. | // then parses and returns a list of name-color pairs and optionally description. | ||||
return nil, fmt.Errorf("line is malformed: %s", line) | return nil, fmt.Errorf("line is malformed: %s", line) | ||||
} | } | ||||
if !labelColorPattern.MatchString(fields[0]) { | |||||
color := strings.Trim(fields[0], " ") | |||||
if len(color) == 6 { | |||||
color = "#" + color | |||||
} | |||||
if !LabelColorPattern.MatchString(color) { | |||||
return nil, fmt.Errorf("bad HTML color code in line: %s", line) | return nil, fmt.Errorf("bad HTML color code in line: %s", line) | ||||
} | } | ||||
} | } | ||||
fields[1] = strings.TrimSpace(fields[1]) | fields[1] = strings.TrimSpace(fields[1]) | ||||
list = append(list, [3]string{fields[1], fields[0], description}) | |||||
list = append(list, [3]string{fields[1], color, description}) | |||||
} | } | ||||
return list, nil | return list, nil | ||||
} | } | ||||
// Label represents a label of repository for issues. | |||||
type Label struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
RepoID int64 `xorm:"INDEX"` | |||||
Name string | |||||
Description string | |||||
Color string `xorm:"VARCHAR(7)"` | |||||
NumIssues int | |||||
NumClosedIssues int | |||||
NumOpenIssues int `xorm:"-"` | |||||
IsChecked bool `xorm:"-"` | |||||
QueryString string `xorm:"-"` | |||||
IsSelected bool `xorm:"-"` | |||||
IsExcluded bool `xorm:"-"` | |||||
} | |||||
// APIFormat converts a Label to the api.Label format | |||||
func (label *Label) APIFormat() *api.Label { | |||||
return &api.Label{ | |||||
ID: label.ID, | |||||
Name: label.Name, | |||||
Color: strings.TrimLeft(label.Color, "#"), | |||||
Description: label.Description, | |||||
} | |||||
} | |||||
// CalOpenIssues calculates the open issues of label. | // CalOpenIssues calculates the open issues of label. | ||||
func (label *Label) CalOpenIssues() { | func (label *Label) CalOpenIssues() { | ||||
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues | label.NumOpenIssues = label.NumIssues - label.NumClosedIssues | ||||
return strings.Join(labels, ", "), err | return strings.Join(labels, ", "), err | ||||
} | } | ||||
func initalizeLabels(e Engine, repoID int64, labelTemplate string) error { | |||||
func initializeLabels(e Engine, repoID int64, labelTemplate string) error { | |||||
list, err := GetLabelTemplateFile(labelTemplate) | list, err := GetLabelTemplateFile(labelTemplate) | ||||
if err != nil { | if err != nil { | ||||
return ErrIssueLabelTemplateLoad{labelTemplate, err} | return ErrIssueLabelTemplateLoad{labelTemplate, err} | ||||
return nil | return nil | ||||
} | } | ||||
// InitalizeLabels adds a label set to a repository using a template | |||||
func InitalizeLabels(ctx DBContext, repoID int64, labelTemplate string) error { | |||||
return initalizeLabels(ctx.e, repoID, labelTemplate) | |||||
// InitializeLabels adds a label set to a repository using a template | |||||
func InitializeLabels(ctx DBContext, repoID int64, labelTemplate string) error { | |||||
return initializeLabels(ctx.e, repoID, labelTemplate) | |||||
} | } | ||||
func newLabel(e Engine, label *Label) error { | func newLabel(e Engine, label *Label) error { | ||||
// NewLabel creates a new label for a repository | // NewLabel creates a new label for a repository | ||||
func NewLabel(label *Label) error { | func NewLabel(label *Label) error { | ||||
if !LabelColorPattern.MatchString(label.Color) { | |||||
return fmt.Errorf("bad color code: %s", label.Color) | |||||
} | |||||
return newLabel(x, label) | return newLabel(x, label) | ||||
} | } | ||||
return err | return err | ||||
} | } | ||||
for _, label := range labels { | for _, label := range labels { | ||||
if !LabelColorPattern.MatchString(label.Color) { | |||||
return fmt.Errorf("bad color code: %s", label.Color) | |||||
} | |||||
if err := newLabel(sess, label); err != nil { | if err := newLabel(sess, label); err != nil { | ||||
return err | return err | ||||
} | } | ||||
// UpdateLabel updates label information. | // UpdateLabel updates label information. | ||||
func UpdateLabel(l *Label) error { | func UpdateLabel(l *Label) error { | ||||
if !LabelColorPattern.MatchString(l.Color) { | |||||
return fmt.Errorf("bad color code: %s", l.Color) | |||||
} | |||||
return updateLabel(x, l) | return updateLabel(x, l) | ||||
} | } | ||||
assert.NoError(t, PrepareTestDatabase()) | assert.NoError(t, PrepareTestDatabase()) | ||||
labels := []*Label{ | labels := []*Label{ | ||||
{RepoID: 2, Name: "labelName2", Color: "#123456"}, | {RepoID: 2, Name: "labelName2", Color: "#123456"}, | ||||
{RepoID: 3, Name: "labelName3", Color: "#234567"}, | |||||
{RepoID: 3, Name: "labelName3", Color: "#23456F"}, | |||||
} | } | ||||
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: ""})) | |||||
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "123456"})) | |||||
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "#12345G"})) | |||||
for _, label := range labels { | for _, label := range labels { | ||||
AssertNotExistsBean(t, label) | AssertNotExistsBean(t, label) | ||||
} | } |
} else if isExist { | } else if isExist { | ||||
return ErrUserAlreadyExist{newUserName} | return ErrUserAlreadyExist{newUserName} | ||||
} | } | ||||
sess := x.NewSession() | sess := x.NewSession() | ||||
defer sess.Close() | defer sess.Close() | ||||
if err = sess.Begin(); err != nil { | if err = sess.Begin(); err != nil { |
// Initialize Issue Labels if selected | // Initialize Issue Labels if selected | ||||
if len(opts.IssueLabels) > 0 { | if len(opts.IssueLabels) > 0 { | ||||
if err = models.InitalizeLabels(ctx, repo.ID, opts.IssueLabels); err != nil { | |||||
return fmt.Errorf("initalizeLabels: %v", err) | |||||
if err = models.InitializeLabels(ctx, repo.ID, opts.IssueLabels); err != nil { | |||||
return fmt.Errorf("InitializeLabels: %v", err) | |||||
} | } | ||||
} | } | ||||
if stdout, err := git.NewCommand("update-server-info"). | if stdout, err := git.NewCommand("update-server-info"). | ||||
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). | SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). | ||||
RunInDir(repoPath); err != nil { | RunInDir(repoPath); err != nil { | ||||
log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) | |||||
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) | |||||
return fmt.Errorf("CreateRepository(git update-server-info): %v", err) | return fmt.Errorf("CreateRepository(git update-server-info): %v", err) | ||||
} | } | ||||
} | } |
Name string `json:"name" binding:"Required"` | Name string `json:"name" binding:"Required"` | ||||
// required:true | // required:true | ||||
// example: #00aabb | // example: #00aabb | ||||
Color string `json:"color" binding:"Required;Size(7)"` | |||||
Color string `json:"color" binding:"Required"` | |||||
Description string `json:"description"` | Description string `json:"description"` | ||||
} | } | ||||
package repo | package repo | ||||
import ( | import ( | ||||
"fmt" | |||||
"net/http" | "net/http" | ||||
"strconv" | "strconv" | ||||
"strings" | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
// responses: | // responses: | ||||
// "201": | // "201": | ||||
// "$ref": "#/responses/Label" | // "$ref": "#/responses/Label" | ||||
// "422": | |||||
// "$ref": "#/responses/validationError" | |||||
form.Color = strings.Trim(form.Color, " ") | |||||
if len(form.Color) == 6 { | |||||
form.Color = "#" + form.Color | |||||
} | |||||
if !models.LabelColorPattern.MatchString(form.Color) { | |||||
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color)) | |||||
return | |||||
} | |||||
label := &models.Label{ | label := &models.Label{ | ||||
Name: form.Name, | Name: form.Name, | ||||
// responses: | // responses: | ||||
// "200": | // "200": | ||||
// "$ref": "#/responses/Label" | // "$ref": "#/responses/Label" | ||||
// "422": | |||||
// "$ref": "#/responses/validationError" | |||||
label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) | label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) | ||||
if err != nil { | if err != nil { | ||||
label.Name = *form.Name | label.Name = *form.Name | ||||
} | } | ||||
if form.Color != nil { | if form.Color != nil { | ||||
label.Color = *form.Color | |||||
label.Color = strings.Trim(*form.Color, " ") | |||||
if len(label.Color) == 6 { | |||||
label.Color = "#" + label.Color | |||||
} | |||||
if !models.LabelColorPattern.MatchString(label.Color) { | |||||
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color)) | |||||
return | |||||
} | |||||
} | } | ||||
if form.Description != nil { | if form.Description != nil { | ||||
label.Description = *form.Description | label.Description = *form.Description |
return | return | ||||
} | } | ||||
if err := models.InitalizeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil { | |||||
if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil { | |||||
if models.IsErrIssueLabelTemplateLoad(err) { | if models.IsErrIssueLabelTemplateLoad(err) { | ||||
originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError | originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError | ||||
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) | ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) | ||||
ctx.Redirect(ctx.Repo.RepoLink + "/labels") | ctx.Redirect(ctx.Repo.RepoLink + "/labels") | ||||
return | return | ||||
} | } | ||||
ctx.ServerError("InitalizeLabels", err) | |||||
ctx.ServerError("InitializeLabels", err) | |||||
return | return | ||||
} | } | ||||
ctx.Redirect(ctx.Repo.RepoLink + "/labels") | ctx.Redirect(ctx.Repo.RepoLink + "/labels") |
"responses": { | "responses": { | ||||
"201": { | "201": { | ||||
"$ref": "#/responses/Label" | "$ref": "#/responses/Label" | ||||
}, | |||||
"422": { | |||||
"$ref": "#/responses/validationError" | |||||
} | } | ||||
} | } | ||||
} | } | ||||
"responses": { | "responses": { | ||||
"200": { | "200": { | ||||
"$ref": "#/responses/Label" | "$ref": "#/responses/Label" | ||||
}, | |||||
"422": { | |||||
"$ref": "#/responses/validationError" | |||||
} | } | ||||
} | } | ||||
} | } |