Merge pull request 'feat: issue edit' (#17) from feat/issue-edit into main
Reviewed-on: https://codeberg.org/romaintb/fgj/pulls/17
This commit is contained in:
commit
a23a4803ce
2 changed files with 256 additions and 1 deletions
79
cmd/issue.go
79
cmd/issue.go
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"codeberg.org/romaintb/fgj/internal/api"
|
"codeberg.org/romaintb/fgj/internal/api"
|
||||||
"codeberg.org/romaintb/fgj/internal/config"
|
"codeberg.org/romaintb/fgj/internal/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var issueCmd = &cobra.Command{
|
var issueCmd = &cobra.Command{
|
||||||
|
|
@ -57,6 +57,14 @@ var issueCloseCmd = &cobra.Command{
|
||||||
RunE: runIssueClose,
|
RunE: runIssueClose,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var issueEditCmd = &cobra.Command{
|
||||||
|
Use: "edit <number>",
|
||||||
|
Short: "Edit an issue",
|
||||||
|
Long: "Edit an existing issue's title, body, or state.",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runIssueEdit,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(issueCmd)
|
rootCmd.AddCommand(issueCmd)
|
||||||
issueCmd.AddCommand(issueListCmd)
|
issueCmd.AddCommand(issueListCmd)
|
||||||
|
|
@ -64,6 +72,7 @@ func init() {
|
||||||
issueCmd.AddCommand(issueCreateCmd)
|
issueCmd.AddCommand(issueCreateCmd)
|
||||||
issueCmd.AddCommand(issueCommentCmd)
|
issueCmd.AddCommand(issueCommentCmd)
|
||||||
issueCmd.AddCommand(issueCloseCmd)
|
issueCmd.AddCommand(issueCloseCmd)
|
||||||
|
issueCmd.AddCommand(issueEditCmd)
|
||||||
|
|
||||||
issueListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
issueListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||||
issueListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
|
issueListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
|
||||||
|
|
@ -78,6 +87,11 @@ func init() {
|
||||||
issueCommentCmd.Flags().StringP("body", "b", "", "Comment body")
|
issueCommentCmd.Flags().StringP("body", "b", "", "Comment body")
|
||||||
|
|
||||||
issueCloseCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
issueCloseCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||||
|
|
||||||
|
issueEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||||
|
issueEditCmd.Flags().StringP("title", "t", "", "New title for the issue")
|
||||||
|
issueEditCmd.Flags().StringP("body", "b", "", "New body for the issue")
|
||||||
|
issueEditCmd.Flags().StringP("state", "s", "", "New state for the issue (open or closed)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssueList(cmd *cobra.Command, args []string) error {
|
func runIssueList(cmd *cobra.Command, args []string) error {
|
||||||
|
|
@ -299,3 +313,66 @@ func runIssueClose(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runIssueEdit(cmd *cobra.Command, args []string) error {
|
||||||
|
repo, _ := cmd.Flags().GetString("repo")
|
||||||
|
title, _ := cmd.Flags().GetString("title")
|
||||||
|
body, _ := cmd.Flags().GetString("body")
|
||||||
|
stateStr, _ := cmd.Flags().GetString("state")
|
||||||
|
|
||||||
|
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid issue number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, name, err := parseRepo(repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if title == "" && body == "" && stateStr == "" {
|
||||||
|
return fmt.Errorf("at least one of --title, --body, or --state must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := api.NewClientFromConfig(cfg, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
editOpt := gitea.EditIssueOption{}
|
||||||
|
|
||||||
|
if title != "" {
|
||||||
|
editOpt.Title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
if body != "" {
|
||||||
|
editOpt.Body = &body
|
||||||
|
}
|
||||||
|
|
||||||
|
if stateStr != "" {
|
||||||
|
switch strings.ToLower(stateStr) {
|
||||||
|
case "open":
|
||||||
|
stateOpen := gitea.StateOpen
|
||||||
|
editOpt.State = &stateOpen
|
||||||
|
case "closed":
|
||||||
|
stateClosed := gitea.StateClosed
|
||||||
|
editOpt.State = &stateClosed
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid state: %s (must be 'open' or 'closed')", stateStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = client.EditIssue(owner, name, issueNumber, editOpt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to edit issue: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Issue #%d updated\n", issueNumber)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,184 @@ func TestCLIIssueClose(t *testing.T) {
|
||||||
t.Logf("Successfully tested issue close via API for CLI #%d", issueNum)
|
t.Logf("Successfully tested issue close via API for CLI #%d", issueNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEditIssueTitle verifies we can edit an issue's title
|
||||||
|
func TestEditIssueTitle(t *testing.T) {
|
||||||
|
env := NewTestEnv(t)
|
||||||
|
|
||||||
|
// Create a test issue
|
||||||
|
issueNum := env.CreateTestIssue(
|
||||||
|
"[FGJ E2E Test] Original Title",
|
||||||
|
"This issue's title will be edited",
|
||||||
|
)
|
||||||
|
|
||||||
|
defer env.CleanupIssue(issueNum)
|
||||||
|
|
||||||
|
// Edit the title
|
||||||
|
newTitle := "[FGJ E2E Test] Updated Title"
|
||||||
|
_, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{
|
||||||
|
Title: newTitle,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to edit issue title: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the title was updated
|
||||||
|
issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get issue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.Title != newTitle {
|
||||||
|
t.Fatalf("expected title '%s', got '%s'", newTitle, issue.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully edited issue #%d title", issueNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEditIssueBody verifies we can edit an issue's body
|
||||||
|
func TestEditIssueBody(t *testing.T) {
|
||||||
|
env := NewTestEnv(t)
|
||||||
|
|
||||||
|
// Create a test issue
|
||||||
|
issueNum := env.CreateTestIssue(
|
||||||
|
"[FGJ E2E Test] Edit Body Test",
|
||||||
|
"Original body content",
|
||||||
|
)
|
||||||
|
|
||||||
|
defer env.CleanupIssue(issueNum)
|
||||||
|
|
||||||
|
// Edit the body
|
||||||
|
newBody := "Updated body content from functional test"
|
||||||
|
_, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{
|
||||||
|
Body: &newBody,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to edit issue body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the body was updated
|
||||||
|
issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get issue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.Body != newBody {
|
||||||
|
t.Fatalf("expected body '%s', got '%s'", newBody, issue.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully edited issue #%d body", issueNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEditIssueState verifies we can edit an issue's state
|
||||||
|
func TestEditIssueState(t *testing.T) {
|
||||||
|
env := NewTestEnv(t)
|
||||||
|
|
||||||
|
// Create a test issue (starts as open)
|
||||||
|
issueNum := env.CreateTestIssue(
|
||||||
|
"[FGJ E2E Test] Edit State Test",
|
||||||
|
"This issue's state will be changed",
|
||||||
|
)
|
||||||
|
|
||||||
|
defer env.CleanupIssue(issueNum)
|
||||||
|
|
||||||
|
// Verify it starts as open
|
||||||
|
issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get issue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.State != "open" {
|
||||||
|
t.Fatalf("expected initial state 'open', got '%s'", issue.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit state to closed
|
||||||
|
closedState := gitea.StateClosed
|
||||||
|
_, _, err = env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{
|
||||||
|
State: &closedState,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to edit issue state: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify state changed to closed
|
||||||
|
issue, _, err = env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get issue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.State != "closed" {
|
||||||
|
t.Fatalf("expected state 'closed' after edit, got '%s'", issue.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit state back to open
|
||||||
|
openState := gitea.StateOpen
|
||||||
|
_, _, err = env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{
|
||||||
|
State: &openState,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to reopen issue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify state is now open
|
||||||
|
issue, _, err = env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get issue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.State != "open" {
|
||||||
|
t.Fatalf("expected state 'open' after reopen, got '%s'", issue.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully edited issue #%d state", issueNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEditIssueMultipleFields verifies we can edit multiple issue fields at once
|
||||||
|
func TestEditIssueMultipleFields(t *testing.T) {
|
||||||
|
env := NewTestEnv(t)
|
||||||
|
|
||||||
|
// Create a test issue
|
||||||
|
issueNum := env.CreateTestIssue(
|
||||||
|
"[FGJ E2E Test] Original Multi-Edit",
|
||||||
|
"Original body for multi-field edit",
|
||||||
|
)
|
||||||
|
|
||||||
|
defer env.CleanupIssue(issueNum)
|
||||||
|
|
||||||
|
// Edit title, body, and state at once
|
||||||
|
newTitle := "[FGJ E2E Test] Updated Multi-Edit"
|
||||||
|
newBody := "Updated body from multi-field edit test"
|
||||||
|
closedState := gitea.StateClosed
|
||||||
|
|
||||||
|
_, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{
|
||||||
|
Title: newTitle,
|
||||||
|
Body: &newBody,
|
||||||
|
State: &closedState,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to edit multiple issue fields: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all fields were updated
|
||||||
|
issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get issue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.Title != newTitle {
|
||||||
|
t.Fatalf("expected title '%s', got '%s'", newTitle, issue.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.Body != newBody {
|
||||||
|
t.Fatalf("expected body '%s', got '%s'", newBody, issue.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.State != "closed" {
|
||||||
|
t.Fatalf("expected state 'closed', got '%s'", issue.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully edited multiple fields on issue #%d", issueNum)
|
||||||
|
}
|
||||||
|
|
||||||
// TestCLIPRList verifies the `fgj pr list` command works
|
// TestCLIPRList verifies the `fgj pr list` command works
|
||||||
func TestCLIPRList(t *testing.T) {
|
func TestCLIPRList(t *testing.T) {
|
||||||
env := NewTestEnv(t)
|
env := NewTestEnv(t)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue