From e2faed52919a34319f44908e844c895e31b05c56 Mon Sep 17 00:00:00 2001 From: Hinne Stolzenberg Date: Tue, 17 Feb 2026 12:54:04 +0100 Subject: [PATCH] chore: improve code mark tests and remove unimplemented worklog stubs - Expand italic and strikethrough tests to assert all inline nodes (not just code nodes), matching the thoroughness of the bold test - Expand the bold+codeBlock combo test with full node assertions - Remove unimplemented worklog/tempo command stubs and references from root command, README, and AGENTS.md --- AGENTS.md | 4 +- README.md | 2 +- internal/api/markdown_code_mark_test.go | 118 ++++++++++++++++++++---- internal/cmd/root.go | 5 +- internal/cmd/worklog/add.go | 67 -------------- internal/cmd/worklog/delete.go | 52 ----------- internal/cmd/worklog/edit.go | 56 ----------- internal/cmd/worklog/list.go | 65 ------------- internal/cmd/worklog/worklog.go | 24 ----- 9 files changed, 104 insertions(+), 289 deletions(-) delete mode 100644 internal/cmd/worklog/add.go delete mode 100644 internal/cmd/worklog/delete.go delete mode 100644 internal/cmd/worklog/edit.go delete mode 100644 internal/cmd/worklog/list.go delete mode 100644 internal/cmd/worklog/worklog.go diff --git a/AGENTS.md b/AGENTS.md index 685e959..2a81852 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This document provides guidance for LLM agents using the `atl` CLI tool. ## Overview -`atl` is a command-line tool for Jira, Confluence, and Tempo. All commands support `--json` for structured output, making it ideal for programmatic use. +`atl` is a command-line tool for Jira and Confluence. All commands support `--json` for structured output, making it ideal for programmatic use. ## Authentication @@ -312,7 +312,6 @@ The CLI returns non-zero exit codes on failure. Common errors: ## Limitations -- `worklog` commands are not yet implemented (Tempo API pending) - No automatic pagination for large result sets - Rate limiting may apply for bulk operations @@ -359,7 +358,6 @@ internal/ issue/ # issue view|list|create|edit|transition|comment|assign confluence/ # confluence space|page subcommands board/ # board list|rank - worklog/ # worklog add|list|edit|delete (stubs) config/ # config get|set|list|use-context|current-context|set-alias|delete-alias config/ # Configuration management (~/.config/atlassian/) iostreams/ # I/O abstraction for testability diff --git a/README.md b/README.md index c7a536e..0b5054a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Atlassian CLI (atl) -A command-line tool for working with Jira, Confluence, and Tempo. Designed with LLM-friendly output for easy integration with AI assistants. +A command-line tool for working with Jira and Confluence. Designed with LLM-friendly output for easy integration with AI assistants. ## Installation diff --git a/internal/api/markdown_code_mark_test.go b/internal/api/markdown_code_mark_test.go index ebe0153..4267f5d 100644 --- a/internal/api/markdown_code_mark_test.go +++ b/internal/api/markdown_code_mark_test.go @@ -50,26 +50,84 @@ func TestMarkdownToADF_CodeInsideBold(t *testing.T) { func TestMarkdownToADF_CodeInsideItalic(t *testing.T) { adf := MarkdownToADF("*italic `code` here*") + if len(adf.Content) != 1 { + t.Fatalf("expected 1 block, got %d", len(adf.Content)) + } + para := adf.Content[0] - for _, c := range para.Content { - if hasCodeMark(c) { - if len(c.Marks) != 1 { - t.Errorf("code node should have only code mark, got %v", c.Marks) - } - } + if para.Type != "paragraph" { + t.Fatalf("expected paragraph, got %s", para.Type) + } + + if len(para.Content) != 3 { + b, _ := json.MarshalIndent(adf, "", " ") + t.Fatalf("expected 3 inline nodes, got %d:\n%s", len(para.Content), b) + } + + // "italic " should have em mark + if para.Content[0].Text != "italic " { + t.Errorf("expected 'italic ', got %q", para.Content[0].Text) + } + if len(para.Content[0].Marks) != 1 || para.Content[0].Marks[0].Type != "em" { + t.Errorf("expected [em] marks, got %v", para.Content[0].Marks) + } + + // "code" should have ONLY code mark + if para.Content[1].Text != "code" { + t.Errorf("expected 'code', got %q", para.Content[1].Text) + } + if len(para.Content[1].Marks) != 1 || para.Content[1].Marks[0].Type != "code" { + t.Errorf("expected [code] marks only, got %v", para.Content[1].Marks) + } + + // " here" should have em mark + if para.Content[2].Text != " here" { + t.Errorf("expected ' here', got %q", para.Content[2].Text) + } + if len(para.Content[2].Marks) != 1 || para.Content[2].Marks[0].Type != "em" { + t.Errorf("expected [em] marks, got %v", para.Content[2].Marks) } } func TestMarkdownToADF_CodeInsideStrikethrough(t *testing.T) { adf := MarkdownToADF("~~deleted `code` here~~") + if len(adf.Content) != 1 { + t.Fatalf("expected 1 block, got %d", len(adf.Content)) + } + para := adf.Content[0] - for _, c := range para.Content { - if hasCodeMark(c) { - if len(c.Marks) != 1 { - t.Errorf("code node should have only code mark, got %v", c.Marks) - } - } + if para.Type != "paragraph" { + t.Fatalf("expected paragraph, got %s", para.Type) + } + + if len(para.Content) != 3 { + b, _ := json.MarshalIndent(adf, "", " ") + t.Fatalf("expected 3 inline nodes, got %d:\n%s", len(para.Content), b) + } + + // "deleted " should have strike mark + if para.Content[0].Text != "deleted " { + t.Errorf("expected 'deleted ', got %q", para.Content[0].Text) + } + if len(para.Content[0].Marks) != 1 || para.Content[0].Marks[0].Type != "strike" { + t.Errorf("expected [strike] marks, got %v", para.Content[0].Marks) + } + + // "code" should have ONLY code mark + if para.Content[1].Text != "code" { + t.Errorf("expected 'code', got %q", para.Content[1].Text) + } + if len(para.Content[1].Marks) != 1 || para.Content[1].Marks[0].Type != "code" { + t.Errorf("expected [code] marks only, got %v", para.Content[1].Marks) + } + + // " here" should have strike mark + if para.Content[2].Text != " here" { + t.Errorf("expected ' here', got %q", para.Content[2].Text) + } + if len(para.Content[2].Marks) != 1 || para.Content[2].Marks[0].Type != "strike" { + t.Errorf("expected [strike] marks, got %v", para.Content[2].Marks) } } @@ -83,12 +141,38 @@ func TestMarkdownToADF_BoldWithCodeAndCodeBlock(t *testing.T) { t.Fatalf("expected 2 blocks (paragraph + codeBlock), got %d:\n%s", len(adf.Content), b) } - // Verify no code node has additional marks + // Verify paragraph content para := adf.Content[0] - for _, c := range para.Content { - if hasCodeMark(c) && len(c.Marks) > 1 { - t.Errorf("code node should have only code mark, got %v", c.Marks) - } + if para.Type != "paragraph" { + t.Fatalf("expected paragraph, got %s", para.Type) + } + if len(para.Content) != 3 { + b, _ := json.MarshalIndent(para, "", " ") + t.Fatalf("expected 3 inline nodes in paragraph, got %d:\n%s", len(para.Content), b) + } + + // "Migration: Falsche " should have strong mark + if para.Content[0].Text != "Migration: Falsche " { + t.Errorf("expected 'Migration: Falsche ', got %q", para.Content[0].Text) + } + if len(para.Content[0].Marks) != 1 || para.Content[0].Marks[0].Type != "strong" { + t.Errorf("expected [strong] marks, got %v", para.Content[0].Marks) + } + + // "s_action" should have ONLY code mark + if para.Content[1].Text != "s_action" { + t.Errorf("expected 's_action', got %q", para.Content[1].Text) + } + if len(para.Content[1].Marks) != 1 || para.Content[1].Marks[0].Type != "code" { + t.Errorf("expected [code] marks only, got %v", para.Content[1].Marks) + } + + // " korrigieren" should have strong mark + if para.Content[2].Text != " korrigieren" { + t.Errorf("expected ' korrigieren', got %q", para.Content[2].Text) + } + if len(para.Content[2].Marks) != 1 || para.Content[2].Marks[0].Type != "strong" { + t.Errorf("expected [strong] marks, got %v", para.Content[2].Marks) } // Verify code block is present diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 7da4251..5ab74c2 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -10,7 +10,6 @@ import ( configCmd "github.com/enthus-appdev/atl-cli/internal/cmd/config" confluenceCmd "github.com/enthus-appdev/atl-cli/internal/cmd/confluence" issueCmd "github.com/enthus-appdev/atl-cli/internal/cmd/issue" - worklogCmd "github.com/enthus-appdev/atl-cli/internal/cmd/worklog" "github.com/enthus-appdev/atl-cli/internal/iostreams" ) @@ -35,13 +34,12 @@ func Execute(ios *iostreams.IOStreams, buildInfo BuildInfo) int { func NewRootCmd(ios *iostreams.IOStreams, buildInfo BuildInfo) *cobra.Command { cmd := &cobra.Command{ Use: "atl", - Short: "Atlassian CLI - Work with Jira, Confluence, and Tempo from the command line", + Short: "Atlassian CLI - Work with Jira and Confluence from the command line", Long: `atl is a CLI tool for interacting with Atlassian products. It provides commands for: - Jira: View, create, and manage issues - Confluence: Read and edit pages - - Tempo: Log and manage worklogs Get started by running 'atl auth login' to authenticate with your Atlassian account. @@ -66,7 +64,6 @@ Environment variables: cmd.AddCommand(issueCmd.NewCmdIssue(ios)) cmd.AddCommand(boardCmd.NewCmdBoard(ios)) cmd.AddCommand(confluenceCmd.NewCmdConfluence(ios)) - cmd.AddCommand(worklogCmd.NewCmdWorklog(ios)) cmd.AddCommand(configCmd.NewCmdConfig(ios)) cmd.AddCommand(newVersionCmd(ios, buildInfo)) cmd.AddCommand(newCompletionCmd(ios)) diff --git a/internal/cmd/worklog/add.go b/internal/cmd/worklog/add.go deleted file mode 100644 index dd77e85..0000000 --- a/internal/cmd/worklog/add.go +++ /dev/null @@ -1,67 +0,0 @@ -package worklog - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/enthus-appdev/atl-cli/internal/iostreams" -) - -// AddOptions holds the options for the add command. -type AddOptions struct { - IO *iostreams.IOStreams - IssueKey string - Time string - Date string - Description string - StartTime string -} - -// NewCmdAdd creates the add command. -func NewCmdAdd(ios *iostreams.IOStreams) *cobra.Command { - opts := &AddOptions{ - IO: ios, - } - - cmd := &cobra.Command{ - Use: "add ", - Short: "Log time to an issue", - Long: `Add a worklog entry to a Jira issue via Tempo.`, - Example: ` # Log 2 hours to an issue - atl worklog add PROJ-1234 --time 2h - - # Log time for a specific date - atl worklog add PROJ-1234 --time 1h30m --date 2024-01-15 - - # Log time with description - atl worklog add PROJ-1234 --time 2h --description "Implemented feature X" - - # Log time with start time - atl worklog add PROJ-1234 --time 2h --start 09:00`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - opts.IssueKey = args[0] - if opts.Time == "" { - return fmt.Errorf("--time flag is required\n\nExample: atl worklog add PROJ-123 --time 2h") - } - return runAdd(opts) - }, - } - - cmd.Flags().StringVarP(&opts.Time, "time", "t", "", "Time spent (e.g., 2h, 1h30m, 90m) (required)") - cmd.Flags().StringVarP(&opts.Date, "date", "d", "", "Date of work (YYYY-MM-DD, default: today)") - cmd.Flags().StringVar(&opts.Description, "description", "", "Work description") - cmd.Flags().StringVar(&opts.StartTime, "start", "", "Start time (HH:MM)") - - return cmd -} - -func runAdd(opts *AddOptions) error { - // TODO: Implement worklog add - - fmt.Fprintf(opts.IO.Out, "Logging %s to %s\n", opts.Time, opts.IssueKey) - fmt.Fprintln(opts.IO.Out, "Not yet implemented. Please run 'atl auth login' first.") - - return nil -} diff --git a/internal/cmd/worklog/delete.go b/internal/cmd/worklog/delete.go deleted file mode 100644 index e66c295..0000000 --- a/internal/cmd/worklog/delete.go +++ /dev/null @@ -1,52 +0,0 @@ -package worklog - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/enthus-appdev/atl-cli/internal/iostreams" -) - -// DeleteOptions holds the options for the delete command. -type DeleteOptions struct { - IO *iostreams.IOStreams - WorklogID string - Confirm bool -} - -// NewCmdDelete creates the delete command. -func NewCmdDelete(ios *iostreams.IOStreams) *cobra.Command { - opts := &DeleteOptions{ - IO: ios, - } - - cmd := &cobra.Command{ - Use: "delete ", - Short: "Delete a worklog", - Long: `Delete a Tempo worklog entry.`, - Example: ` # Delete a worklog (will prompt for confirmation) - atl worklog delete 12345 - - # Delete without confirmation - atl worklog delete 12345 --yes`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - opts.WorklogID = args[0] - return runDelete(opts) - }, - } - - cmd.Flags().BoolVarP(&opts.Confirm, "yes", "y", false, "Skip confirmation prompt") - - return cmd -} - -func runDelete(opts *DeleteOptions) error { - // TODO: Implement worklog delete - - fmt.Fprintf(opts.IO.Out, "Deleting worklog: %s\n", opts.WorklogID) - fmt.Fprintln(opts.IO.Out, "Not yet implemented. Please run 'atl auth login' first.") - - return nil -} diff --git a/internal/cmd/worklog/edit.go b/internal/cmd/worklog/edit.go deleted file mode 100644 index c1cca92..0000000 --- a/internal/cmd/worklog/edit.go +++ /dev/null @@ -1,56 +0,0 @@ -package worklog - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/enthus-appdev/atl-cli/internal/iostreams" -) - -// EditOptions holds the options for the edit command. -type EditOptions struct { - IO *iostreams.IOStreams - WorklogID string - Time string - Description string - Date string -} - -// NewCmdEdit creates the edit command. -func NewCmdEdit(ios *iostreams.IOStreams) *cobra.Command { - opts := &EditOptions{ - IO: ios, - } - - cmd := &cobra.Command{ - Use: "edit ", - Short: "Edit a worklog", - Long: `Edit an existing Tempo worklog entry.`, - Example: ` # Update time - atl worklog edit 12345 --time 3h - - # Update description - atl worklog edit 12345 --description "Updated work description"`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - opts.WorklogID = args[0] - return runEdit(opts) - }, - } - - cmd.Flags().StringVarP(&opts.Time, "time", "t", "", "New time spent") - cmd.Flags().StringVar(&opts.Description, "description", "", "New description") - cmd.Flags().StringVarP(&opts.Date, "date", "d", "", "New date") - - return cmd -} - -func runEdit(opts *EditOptions) error { - // TODO: Implement worklog edit - - fmt.Fprintf(opts.IO.Out, "Editing worklog: %s\n", opts.WorklogID) - fmt.Fprintln(opts.IO.Out, "Not yet implemented. Please run 'atl auth login' first.") - - return nil -} diff --git a/internal/cmd/worklog/list.go b/internal/cmd/worklog/list.go deleted file mode 100644 index 2704c71..0000000 --- a/internal/cmd/worklog/list.go +++ /dev/null @@ -1,65 +0,0 @@ -package worklog - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/enthus-appdev/atl-cli/internal/iostreams" -) - -// ListOptions holds the options for the list command. -type ListOptions struct { - IO *iostreams.IOStreams - From string - To string - IssueKey string - Limit int - JSON bool -} - -// NewCmdList creates the list command. -func NewCmdList(ios *iostreams.IOStreams) *cobra.Command { - opts := &ListOptions{ - IO: ios, - Limit: 50, - } - - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List worklogs", - Long: `List Tempo worklogs for a date range or issue.`, - Example: ` # List worklogs for today - atl worklog list - - # List worklogs for a date range - atl worklog list --from 2024-01-01 --to 2024-01-31 - - # List worklogs for a specific issue - atl worklog list --issue PROJ-1234 - - # List this week's worklogs - atl worklog list --from monday`, - RunE: func(cmd *cobra.Command, args []string) error { - return runList(opts) - }, - } - - cmd.Flags().StringVar(&opts.From, "from", "", "Start date (YYYY-MM-DD or 'today', 'monday', etc.)") - cmd.Flags().StringVar(&opts.To, "to", "", "End date (YYYY-MM-DD)") - cmd.Flags().StringVarP(&opts.IssueKey, "issue", "i", "", "Filter by issue key") - cmd.Flags().IntVarP(&opts.Limit, "limit", "l", 50, "Maximum number of worklogs to return") - cmd.Flags().BoolVarP(&opts.JSON, "json", "j", false, "Output as JSON") - - return cmd -} - -func runList(opts *ListOptions) error { - // TODO: Implement worklog list - - fmt.Fprintln(opts.IO.Out, "Listing worklogs...") - fmt.Fprintln(opts.IO.Out, "Not yet implemented. Please run 'atl auth login' first.") - - return nil -} diff --git a/internal/cmd/worklog/worklog.go b/internal/cmd/worklog/worklog.go deleted file mode 100644 index f20bbc8..0000000 --- a/internal/cmd/worklog/worklog.go +++ /dev/null @@ -1,24 +0,0 @@ -package worklog - -import ( - "github.com/spf13/cobra" - - "github.com/enthus-appdev/atl-cli/internal/iostreams" -) - -// NewCmdWorklog creates the worklog command group. -func NewCmdWorklog(ios *iostreams.IOStreams) *cobra.Command { - cmd := &cobra.Command{ - Use: "worklog", - Aliases: []string{"wl", "log"}, - Short: "Work with Tempo worklogs", - Long: `Log time, view, and manage Tempo worklogs.`, - } - - cmd.AddCommand(NewCmdAdd(ios)) - cmd.AddCommand(NewCmdList(ios)) - cmd.AddCommand(NewCmdEdit(ios)) - cmd.AddCommand(NewCmdDelete(ios)) - - return cmd -}