mirror of
https://gitea.com/gitea/tea.git
synced 2026-08-05 23:07:39 +02:00
feat(assignees): add set, add, and remove assignees APIs (#1045)
Implemented set, add, and remove assignees APIs. Closes https://gitea.com/gitea/tea/issues/965 and https://gitea.com/gitea/tea/issues/966Reviewed-on: https://gitea.com/gitea/tea/pulls/1045 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Minjie Fang <wingsallen@gmail.com>
This commit is contained in:
@@ -51,7 +51,7 @@ jobs:
|
||||
make integration-test
|
||||
services:
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:1.26.2
|
||||
image: docker.gitea.com/gitea:1.27.0
|
||||
cmd:
|
||||
- bash
|
||||
- -c
|
||||
|
||||
+20
-4
@@ -177,10 +177,18 @@ func GetIssuePRCreateFlags(requestCtx stdctx.Context, ctx *context.TeaContext) (
|
||||
|
||||
// IssuePREditFlags defines flags for editing properties of issues and PRs
|
||||
var IssuePREditFlags = append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "set-assignees",
|
||||
Usage: "Clear all existing assignees and assign comma-separated list of usernames. Takes precedence over --add-assignees and --remove-assignees",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "add-assignees",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Comma-separated list of usernames to assign",
|
||||
Usage: "Comma-separated list of usernames to assign. Takes precedence over --remove-assignees",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "remove-assignees",
|
||||
Usage: "Comma-separated list of usernames to remove",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "add-labels",
|
||||
@@ -224,17 +232,25 @@ func GetIssuePREditFlags(ctx *context.TeaContext) (*task.EditIssueOption, error)
|
||||
opts.Deadline = &t
|
||||
}
|
||||
}
|
||||
if ctx.IsSet("set-assignees") {
|
||||
val := ctx.String("set-assignees")
|
||||
opts.SetAssignees = strings.Split(strings.ReplaceAll(val, " ", ""), ",")
|
||||
}
|
||||
if ctx.IsSet("add-assignees") {
|
||||
val := ctx.String("add-assignees")
|
||||
opts.AddAssignees = strings.Split(val, ",")
|
||||
opts.AddAssignees = strings.Split(strings.ReplaceAll(val, " ", ""), ",")
|
||||
}
|
||||
if ctx.IsSet("remove-assignees") {
|
||||
val := ctx.String("remove-assignees")
|
||||
opts.RemoveAssignees = strings.Split(strings.ReplaceAll(val, " ", ""), ",")
|
||||
}
|
||||
if ctx.IsSet("add-labels") {
|
||||
val := ctx.String("add-labels")
|
||||
opts.AddLabels = strings.Split(val, ",")
|
||||
opts.AddLabels = strings.Split(strings.ReplaceAll(val, " ", ""), ",")
|
||||
}
|
||||
if ctx.IsSet("remove-labels") {
|
||||
val := ctx.String("remove-labels")
|
||||
opts.RemoveLabels = strings.Split(val, ",")
|
||||
opts.RemoveLabels = strings.Split(strings.ReplaceAll(val, " ", ""), ",")
|
||||
}
|
||||
return &opts, nil
|
||||
}
|
||||
|
||||
+10
-2
@@ -239,7 +239,7 @@ Create an issue on repository
|
||||
|
||||
Edit one or more issues
|
||||
|
||||
**--add-assignees, -a**="": Comma-separated list of usernames to assign
|
||||
**--add-assignees, -a**="": Comma-separated list of usernames to assign. Takes precedence over --remove-assignees
|
||||
|
||||
**--add-labels, -L**="": Comma-separated list of labels to assign. Takes precedence over --remove-labels
|
||||
|
||||
@@ -255,10 +255,14 @@ Edit one or more issues
|
||||
|
||||
**--remote, -R**="": Discover Gitea login from remote. Optional
|
||||
|
||||
**--remove-assignees**="": Comma-separated list of usernames to remove
|
||||
|
||||
**--remove-labels**="": Comma-separated list of labels to remove
|
||||
|
||||
**--repo, -r**="": Override local repository path or gitea repository slug to interact with. Optional
|
||||
|
||||
**--set-assignees**="": Clear all existing assignees and assign comma-separated list of usernames. Takes precedence over --add-assignees and --remove-assignees
|
||||
|
||||
**--title, -t**="":
|
||||
|
||||
### reopen, open
|
||||
@@ -423,7 +427,7 @@ Change state of one or more pull requests to 'open'
|
||||
|
||||
Edit one or more pull requests
|
||||
|
||||
**--add-assignees, -a**="": Comma-separated list of usernames to assign
|
||||
**--add-assignees, -a**="": Comma-separated list of usernames to assign. Takes precedence over --remove-assignees
|
||||
|
||||
**--add-labels, -L**="": Comma-separated list of labels to assign. Takes precedence over --remove-labels
|
||||
|
||||
@@ -445,12 +449,16 @@ Edit one or more pull requests
|
||||
|
||||
**--remote, -R**="": Discover Gitea login from remote. Optional
|
||||
|
||||
**--remove-assignees**="": Comma-separated list of usernames to remove
|
||||
|
||||
**--remove-labels**="": Comma-separated list of labels to remove
|
||||
|
||||
**--remove-reviewers**="": Comma-separated list of usernames to remove from reviewers
|
||||
|
||||
**--repo, -r**="": Override local repository path or gitea repository slug to interact with. Optional
|
||||
|
||||
**--set-assignees**="": Clear all existing assignees and assign comma-separated list of usernames. Takes precedence over --add-assignees and --remove-assignees
|
||||
|
||||
**--title, -t**="":
|
||||
|
||||
### review
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"gitea.dev/tea/modules/config"
|
||||
"gitea.dev/tea/modules/context"
|
||||
"gitea.dev/tea/modules/task"
|
||||
@@ -40,9 +41,10 @@ func EditIssue(requestCtx stdctx.Context, ctx context.TeaContext, index int64) (
|
||||
Deadline: i.Deadline,
|
||||
}
|
||||
|
||||
i.Assignees = cleanAssignees(i.Assignees)
|
||||
if len(i.Assignees) != 0 {
|
||||
for _, a := range i.Assignees {
|
||||
opts.AddAssignees = append(opts.AddAssignees, a.UserName)
|
||||
opts.SetAssignees = append(opts.SetAssignees, a.UserName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +111,7 @@ func promptIssueEditProperties(requestCtx stdctx.Context, ctx *context.TeaContex
|
||||
return nil
|
||||
}
|
||||
|
||||
currAssignees := o.AddAssignees
|
||||
currAssignees := o.SetAssignees
|
||||
newAssignees := selectables.Assignees
|
||||
|
||||
for _, c := range currAssignees {
|
||||
@@ -119,10 +121,14 @@ func promptIssueEditProperties(requestCtx stdctx.Context, ctx *context.TeaContex
|
||||
}
|
||||
|
||||
// assignees
|
||||
if o.AddAssignees, err = promptMultiSelect("Add Assignees:", newAssignees, "[other]"); err != nil {
|
||||
if currAssignees, err = promptMultiSelectWithPreselect("Set Assignees:", currAssignees, newAssignees, "[other]"); err != nil {
|
||||
return err
|
||||
}
|
||||
printTitleAndContent("Assignees:", strings.Join(o.AddAssignees, "\n"))
|
||||
if len(currAssignees) == 0 && len(o.SetAssignees) > 0 {
|
||||
o.RemoveAssignees = o.SetAssignees
|
||||
}
|
||||
o.SetAssignees = currAssignees
|
||||
printTitleAndContent("Assignees:", strings.Join(o.SetAssignees, "\n"))
|
||||
|
||||
// milestone
|
||||
if len(selectables.MilestoneList) != 0 {
|
||||
@@ -175,3 +181,13 @@ func promptIssueEditProperties(requestCtx stdctx.Context, ctx *context.TeaContex
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanAssignees(list []*gitea.User) []*gitea.User {
|
||||
out := make([]*gitea.User, 0, len(list))
|
||||
for _, a := range list {
|
||||
if strings.TrimSpace(a.UserName) != "" {
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -92,10 +92,26 @@ func promptDatetime(prompt string) (val *time.Time, err error) {
|
||||
|
||||
// promptSelect creates a generic multiselect prompt, with processing of custom values.
|
||||
func promptMultiSelect(prompt string, options []string, customVal string) ([]string, error) {
|
||||
opts := huh.NewOptions(makeSelectOpts(options, customVal, "")...)
|
||||
return runMultiSelect(prompt, opts, customVal)
|
||||
}
|
||||
|
||||
// promptMultiSelectWithPreselect creates a generic multiselect prompt with preselected values and processing of custom values.
|
||||
func promptMultiSelectWithPreselect(prompt string, selected []string, options []string, customVal string) ([]string, error) {
|
||||
opts := make([]huh.Option[string], 0, len(selected)+len(options)+1)
|
||||
for _, name := range selected {
|
||||
opts = append(opts, huh.NewOption(name, name).Selected(true))
|
||||
}
|
||||
opts = append(opts, huh.NewOptions(makeSelectOpts(options, customVal, "")...)...)
|
||||
|
||||
return runMultiSelect(prompt, opts, customVal)
|
||||
}
|
||||
|
||||
func runMultiSelect(prompt string, opts []huh.Option[string], customVal string) ([]string, error) {
|
||||
var selection []string
|
||||
if err := huh.NewMultiSelect[string]().
|
||||
Title(prompt).
|
||||
Options(huh.NewOptions(makeSelectOpts(options, customVal, "")...)...).
|
||||
Options(opts...).
|
||||
Value(&selection).
|
||||
WithTheme(theme.GetTheme()).
|
||||
Run(); err != nil {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package task
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
)
|
||||
|
||||
// ResolveAssigneeOpts resolves assignee names to IssueAssigneesOption. Returns nil if names is empty.
|
||||
func ResolveAssigneeOpts(names []string) *gitea.IssueAssigneesOption {
|
||||
names = cleanAssignees(names)
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &gitea.IssueAssigneesOption{Assignees: names}
|
||||
}
|
||||
|
||||
// ApplyAssigneeChanges adds and removes assignees on an issue or pull request.
|
||||
func ApplyAssigneeChanges(requestCtx stdctx.Context, client *gitea.Client, owner, repo string, index int64, add, rm *gitea.IssueAssigneesOption) error {
|
||||
if rm != nil {
|
||||
_, _, err := client.Issues.DeleteIssueAssignees(requestCtx, owner, repo, index, *rm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not remove assignees: %s", err)
|
||||
}
|
||||
}
|
||||
if add != nil {
|
||||
_, _, err := client.Issues.AddIssueAssignees(requestCtx, owner, repo, index, *add)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add assignees: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanAssignees(list []string) []string {
|
||||
out := make([]string, 0, len(list))
|
||||
for _, a := range list {
|
||||
if strings.TrimSpace(a) != "" {
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
+18
-10
@@ -23,23 +23,26 @@ type EditIssueOption struct {
|
||||
Deadline *time.Time
|
||||
AddLabels []string
|
||||
RemoveLabels []string
|
||||
SetAssignees []string
|
||||
AddAssignees []string
|
||||
RemoveAssignees []string
|
||||
AddReviewers []string
|
||||
RemoveReviewers []string
|
||||
// RemoveAssignees []string // NOTE: with the current go-sdk, clearing assignees is not possible.
|
||||
}
|
||||
|
||||
// Normalizes the options into parameters that can be passed to the sdk.
|
||||
// the returned value will be nil, when no change to this part of the issue is requested.
|
||||
func (o EditIssueOption) toSdkOptions(requestCtx stdctx.Context, ctx *context.TeaContext, client *gitea.Client) (*gitea.EditIssueOption, *gitea.IssueLabelsOption, *gitea.IssueLabelsOption, error) {
|
||||
func (o EditIssueOption) toSdkOptions(requestCtx stdctx.Context, ctx *context.TeaContext, client *gitea.Client) (*gitea.EditIssueOption, *gitea.IssueLabelsOption, *gitea.IssueLabelsOption, *gitea.IssueAssigneesOption, *gitea.IssueAssigneesOption, error) {
|
||||
addLabelOpts, err := ResolveLabelOpts(requestCtx, client, ctx.Owner, ctx.Repo, o.AddLabels)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
rmLabelOpts, err := ResolveLabelOpts(requestCtx, client, ctx.Owner, ctx.Repo, o.RemoveLabels)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
addAssigneeOpts := ResolveAssigneeOpts(o.AddAssignees)
|
||||
rmAssigneeOpts := ResolveAssigneeOpts(o.RemoveAssignees)
|
||||
|
||||
issueOpts := gitea.EditIssueOption{}
|
||||
var issueOptsDirty bool
|
||||
@@ -58,7 +61,7 @@ func (o EditIssueOption) toSdkOptions(requestCtx stdctx.Context, ctx *context.Te
|
||||
if o.Milestone != nil {
|
||||
id, err := ResolveMilestoneID(requestCtx, client, ctx.Owner, ctx.Repo, *o.Milestone)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
issueOpts.Milestone = gitea.OptionalInt64(id)
|
||||
issueOptsDirty = true
|
||||
@@ -70,15 +73,16 @@ func (o EditIssueOption) toSdkOptions(requestCtx stdctx.Context, ctx *context.Te
|
||||
issueOpts.RemoveDeadline = gitea.OptionalBool(true)
|
||||
}
|
||||
}
|
||||
if len(o.AddAssignees) != 0 {
|
||||
issueOpts.Assignees = o.AddAssignees
|
||||
o.SetAssignees = cleanAssignees(o.SetAssignees)
|
||||
if len(o.SetAssignees) != 0 {
|
||||
issueOpts.Assignees = o.SetAssignees
|
||||
issueOptsDirty = true
|
||||
}
|
||||
|
||||
if issueOptsDirty {
|
||||
return &issueOpts, addLabelOpts, rmLabelOpts, nil
|
||||
return &issueOpts, addLabelOpts, rmLabelOpts, addAssigneeOpts, rmAssigneeOpts, nil
|
||||
}
|
||||
return nil, addLabelOpts, rmLabelOpts, nil
|
||||
return nil, addLabelOpts, rmLabelOpts, addAssigneeOpts, rmAssigneeOpts, nil
|
||||
}
|
||||
|
||||
// EditIssue edits an issue and returns the updated issue.
|
||||
@@ -87,7 +91,7 @@ func EditIssue(requestCtx stdctx.Context, ctx *context.TeaContext, client *gitea
|
||||
client = ctx.Login.Client()
|
||||
}
|
||||
|
||||
issueOpts, addLabelOpts, rmLabelOpts, err := opts.toSdkOptions(requestCtx, ctx, client)
|
||||
issueOpts, addLabelOpts, rmLabelOpts, addAssigneeOpts, rmAssigneeOpts, err := opts.toSdkOptions(requestCtx, ctx, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -96,6 +100,10 @@ func EditIssue(requestCtx stdctx.Context, ctx *context.TeaContext, client *gitea
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ApplyAssigneeChanges(requestCtx, client, ctx.Owner, ctx.Repo, opts.Index, addAssigneeOpts, rmAssigneeOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var issue *gitea.Issue
|
||||
if issueOpts != nil {
|
||||
issue, _, err = client.Issues.EditIssue(requestCtx, ctx.Owner, ctx.Repo, opts.Index, *issueOpts)
|
||||
|
||||
@@ -25,6 +25,8 @@ func EditPull(requestCtx stdctx.Context, ctx *context.TeaContext, client *gitea.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addAssigneeOpts := ResolveAssigneeOpts(opts.AddAssignees)
|
||||
rmAssigneeOpts := ResolveAssigneeOpts(opts.RemoveAssignees)
|
||||
|
||||
prOpts := gitea.EditPullRequestOption{}
|
||||
var prOptsDirty bool
|
||||
@@ -51,8 +53,9 @@ func EditPull(requestCtx stdctx.Context, ctx *context.TeaContext, client *gitea.
|
||||
prOpts.RemoveDeadline = gitea.OptionalBool(true)
|
||||
}
|
||||
}
|
||||
if len(opts.AddAssignees) != 0 {
|
||||
prOpts.Assignees = opts.AddAssignees
|
||||
opts.SetAssignees = cleanAssignees(opts.SetAssignees)
|
||||
if len(opts.SetAssignees) != 0 {
|
||||
prOpts.Assignees = opts.SetAssignees
|
||||
prOptsDirty = true
|
||||
}
|
||||
|
||||
@@ -60,6 +63,10 @@ func EditPull(requestCtx stdctx.Context, ctx *context.TeaContext, client *gitea.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ApplyAssigneeChanges(requestCtx, client, ctx.Owner, ctx.Repo, opts.Index, addAssigneeOpts, rmAssigneeOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ApplyReviewerChanges(requestCtx, client, ctx.Owner, ctx.Repo, opts.Index, opts.AddReviewers, opts.RemoveReviewers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"gitea.dev/tea/cmd"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEditIssue_ModifiesAssignees(t *testing.T) {
|
||||
// This test verifies that EditIssue correctly modifies assignees of an issue via set, add, and remove.
|
||||
// It sets up a test repository and organization with known users, then calls EditIssue and checks the results.
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
orgName := fmt.Sprintf("issue-org-%d", time.Now().UnixNano()%1_000_000)
|
||||
orgRepoName := fmt.Sprintf("issue-repo-%d", time.Now().UnixNano()%1_000_000)
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test data that might interfere with the test.
|
||||
_, _ = client.Repositories.DeleteRepo(ctx, orgName, orgRepoName)
|
||||
_, _ = client.Organizations.DeleteOrg(ctx, orgName)
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
|
||||
_, _, err := client.Admin.CreateOrg(ctx, integrationUsername, gitea.CreateOrgOption{Name: orgName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Organizations.DeleteOrg(ctx, orgName); delErr != nil {
|
||||
t.Logf("failed to delete integration test org %q: %v", orgName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgRepo, _, err := client.Repositories.CreateOrgRepo(ctx, orgName, gitea.CreateRepoOption{Name: orgRepoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(ctx, orgName, orgRepoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", orgRepoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
user1, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user1", Password: "user1!1234", Email: "user1@test.com"})
|
||||
require.NoError(t, err)
|
||||
user2, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user2", Password: "user2!1234", Email: "user2@test.com"})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
})
|
||||
|
||||
permission := gitea.AccessModeOwner
|
||||
team, _, err := client.Organizations.CreateTeam(ctx, orgName, gitea.CreateTeamOption{Name: "writers", Permission: permission})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user1")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user2")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamRepository(ctx, team.ID, orgName, orgRepoName)
|
||||
require.NoError(t, err)
|
||||
assigneeValid, _, err := client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user1.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
assigneeValid, _, err = client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user2.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
|
||||
orgIssue, _, err := client.Issues.CreateIssue(ctx, orgName, orgRepoName, gitea.CreateIssueOption{Title: "issue_integration_test", Assignees: []string{integrationUsername}, Closed: false})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, integrationUsername, orgIssue.Assignees[0].UserName)
|
||||
curUser, _, err := client.Users.GetMyUserInfo(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
getNames := func(issue *gitea.Issue) []string {
|
||||
names := make([]string, len(issue.Assignees))
|
||||
for i, u := range issue.Assignees {
|
||||
names[i] = u.UserName
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
checkAssignees := func(expected []string) {
|
||||
updatedIssue, _, err := client.Issues.GetIssue(ctx, orgName, orgRepoName, orgIssue.Index)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, getNames(updatedIssue), expected)
|
||||
}
|
||||
|
||||
app := cmd.App()
|
||||
// test set overwrites add and remove
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user1", "--add-assignees", "user2", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user2," + integrationUsername, "--add-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user2.UserName, curUser.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user1", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
|
||||
// test remove one assignee
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--remove-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
// test add multiple assignees and overwrites remove
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--add-assignees", "user1," + integrationUsername, "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName})
|
||||
|
||||
// test add one assignee
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--add-assignees", "user2", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName, user2.UserName})
|
||||
|
||||
// test remove multiple assignees
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--remove-assignees", "user1,user2," + integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--add-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--remove-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func TestResolveLabelNames_ReturnsRepoAndOrgLabels(t *testing.T) {
|
||||
runGit("commit", "--allow-empty", "-m", "Initial commit")
|
||||
runGit("push", "-u", "origin", "HEAD:branch-with-labels")
|
||||
|
||||
waitForBranches(t, orgRepo.FullName)
|
||||
waitForBranches(t, orgRepo.FullName, "branch-with-labels")
|
||||
_ = runTeaCommand(
|
||||
t, "pr", "create", "--repo", orgRepo.FullName,
|
||||
"--login", login.Name, "--base", "main", "--head", "branch-with-labels",
|
||||
@@ -94,7 +94,7 @@ func TestResolveLabelNames_ReturnsRepoAndOrgLabels(t *testing.T) {
|
||||
require.ElementsMatch(t, labels, []*gitea.Label{orgLabel, repoLabel})
|
||||
}
|
||||
|
||||
func waitForBranches(t *testing.T, repoFullName string) {
|
||||
func waitForBranches(t *testing.T, repoFullName string, branchName string) {
|
||||
t.Helper()
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/branches", os.Getenv("GITEA_TEA_TEST_URL"), repoFullName)
|
||||
|
||||
@@ -112,7 +112,7 @@ func waitForBranches(t *testing.T, repoFullName string) {
|
||||
for _, b := range branches {
|
||||
have[b.Name] = true
|
||||
}
|
||||
if have["main"] && have["branch-with-labels"] {
|
||||
if have["main"] && have[branchName] {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"gitea.dev/tea/cmd"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEditPull_ModifiesAssignees(t *testing.T) {
|
||||
// This test verifies that EditPull correctly modifies assignees of an pull request via set, add, and remove.
|
||||
// It sets up a test repository and organization with known users, then calls EditPull and checks the results.
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
orgName := fmt.Sprintf("pull-org-%d", time.Now().UnixNano()%1_000_000)
|
||||
orgRepoName := fmt.Sprintf("pull-repo-%d", time.Now().UnixNano()%1_000_000)
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test data that might interfere with the test.
|
||||
_, _ = client.Repositories.DeleteRepo(ctx, orgName, orgRepoName)
|
||||
_, _ = client.Organizations.DeleteOrg(ctx, orgName)
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
|
||||
_, _, err := client.Admin.CreateOrg(ctx, integrationUsername, gitea.CreateOrgOption{Name: orgName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Organizations.DeleteOrg(ctx, orgName); delErr != nil {
|
||||
t.Logf("failed to delete integration test org %q: %v", orgName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgRepo, _, err := client.Repositories.CreateOrgRepo(ctx, orgName, gitea.CreateRepoOption{Name: orgRepoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(ctx, orgName, orgRepoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", orgRepoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
user1, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user1", Password: "user1!1234", Email: "user1@test.com"})
|
||||
require.NoError(t, err)
|
||||
user2, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user2", Password: "user2!1234", Email: "user2@test.com"})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
})
|
||||
|
||||
permission := gitea.AccessModeOwner
|
||||
team, _, err := client.Organizations.CreateTeam(ctx, orgName, gitea.CreateTeamOption{Name: "writers", Permission: permission})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user1")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user2")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamRepository(ctx, team.ID, orgName, orgRepoName)
|
||||
require.NoError(t, err)
|
||||
assigneeValid, _, err := client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user1.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
assigneeValid, _, err = client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user2.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
runGit := func(args ...string) {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = tmpDir
|
||||
require.NoError(t, cmd.Run())
|
||||
}
|
||||
|
||||
runGit("init")
|
||||
runGit("config", "user.email", "test@test.com")
|
||||
runGit("config", "user.name", "test")
|
||||
httpsURL := fmt.Sprintf("%s/%s.git", login.URL, orgRepo.FullName)
|
||||
httpsURL = strings.Replace(httpsURL, "://", fmt.Sprintf("://%s:%s@", login.Name, login.Token), 1)
|
||||
|
||||
runGit("remote", "add", "origin", httpsURL)
|
||||
|
||||
runGit("checkout", "-b", "main")
|
||||
runGit("commit", "--allow-empty", "-m", "Initial commit")
|
||||
runGit("push", "-u", "origin", "HEAD:main")
|
||||
|
||||
runGit("checkout", "-b", "branch-with-assignees")
|
||||
runGit("commit", "--allow-empty", "-m", "Initial commit")
|
||||
runGit("push", "-u", "origin", "HEAD:branch-with-assignees")
|
||||
|
||||
waitForBranches(t, orgRepo.FullName, "branch-with-assignees")
|
||||
getNames := func(pr *gitea.PullRequest) []string {
|
||||
names := make([]string, len(pr.Assignees))
|
||||
for i, u := range pr.Assignees {
|
||||
names[i] = u.UserName
|
||||
}
|
||||
return names
|
||||
}
|
||||
checkAssignees := func(expected []string) {
|
||||
updatedPr, _, err := client.PullRequests.GetPullRequest(ctx, orgName, orgRepoName, 1)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, getNames(updatedPr), expected)
|
||||
}
|
||||
|
||||
app := cmd.App()
|
||||
err = app.Run(ctx, []string{"tea", "pr", "create", "--repo", orgRepo.FullName, "--base", "main", "--head", "branch-with-assignees", "--a", integrationUsername})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{integrationUsername})
|
||||
curUser, _, err := client.Users.GetMyUserInfo(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test set overwrites add and remove
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user1", "--add-assignees", "user2", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user2," + integrationUsername, "--add-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user2.UserName, curUser.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user1", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
|
||||
// test remove one assignee
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--remove-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
// test add multiple assignees and overwrites remove
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--add-assignees", "user1," + integrationUsername, "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName})
|
||||
|
||||
// test add one assignee
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--add-assignees", "user2", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName, user2.UserName})
|
||||
|
||||
// test remove multiple assignees
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--remove-assignees", "user1,user2," + integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--add-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--remove-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user