mirror of
https://github.com/hanzoskill/mogcli.git
synced 2026-10-30 21:03:34 +00:00
658 lines
15 KiB
Go
658 lines
15 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/visionik/mogcli/internal/testutil"
|
|
)
|
|
|
|
func TestTasksListsCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
root *Root
|
|
mockResp []byte
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful list task lists",
|
|
root: &Root{},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "list-123", "displayName": "My Tasks", "isOwner": true},
|
|
{"id": "list-456", "displayName": "Work", "isOwner": false},
|
|
},
|
|
}),
|
|
wantInOut: "My Tasks",
|
|
},
|
|
{
|
|
name: "JSON output",
|
|
root: &Root{JSON: true},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "list-123", "displayName": "Tasks"},
|
|
},
|
|
}),
|
|
wantInOut: `"displayName": "Tasks"`,
|
|
},
|
|
{
|
|
name: "API error",
|
|
root: &Root{},
|
|
mockErr: errors.New("API error"),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mock := &testutil.MockClient{
|
|
GetFunc: func(ctx context.Context, path string, query url.Values) ([]byte, error) {
|
|
return tt.mockResp, tt.mockErr
|
|
},
|
|
}
|
|
tt.root.ClientFactory = mockClientFactory(mock)
|
|
|
|
cmd := &TasksListsCmd{}
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = cmd.Run(tt.root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTasksListCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmd *TasksListCmd
|
|
root *Root
|
|
mockResp []byte
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful list tasks",
|
|
cmd: &TasksListCmd{},
|
|
root: &Root{},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{
|
|
"id": "task-123",
|
|
"title": "Complete project",
|
|
"status": "notStarted",
|
|
"importance": "high",
|
|
"dueDateTime": map[string]string{
|
|
"dateTime": "2024-01-20T00:00:00",
|
|
"timeZone": "UTC",
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
wantInOut: "Complete project",
|
|
},
|
|
{
|
|
name: "list tasks with specific list ID",
|
|
cmd: &TasksListCmd{ListID: "list-123"},
|
|
root: &Root{},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "task-123", "title": "Task", "status": "notStarted"},
|
|
},
|
|
}),
|
|
wantInOut: "Task",
|
|
},
|
|
{
|
|
name: "list all tasks including completed",
|
|
cmd: &TasksListCmd{All: true},
|
|
root: &Root{},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "task-123", "title": "Completed Task", "status": "completed"},
|
|
},
|
|
}),
|
|
wantInOut: "Completed Task",
|
|
},
|
|
{
|
|
name: "no tasks found",
|
|
cmd: &TasksListCmd{},
|
|
root: &Root{},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{},
|
|
}),
|
|
wantInOut: "No tasks",
|
|
},
|
|
{
|
|
name: "JSON output",
|
|
cmd: &TasksListCmd{},
|
|
root: &Root{JSON: true},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "task-123", "title": "Task"},
|
|
},
|
|
}),
|
|
wantInOut: `"title": "Task"`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
callCount := 0
|
|
mock := &testutil.MockClient{
|
|
GetFunc: func(ctx context.Context, path string, query url.Values) ([]byte, error) {
|
|
callCount++
|
|
if tt.mockErr != nil {
|
|
return nil, tt.mockErr
|
|
}
|
|
// First call is for task lists (when no ListID specified)
|
|
if callCount == 1 && tt.cmd.ListID == "" {
|
|
return mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "list-default-123", "displayName": "Tasks"},
|
|
},
|
|
}), nil
|
|
}
|
|
return tt.mockResp, nil
|
|
},
|
|
}
|
|
tt.root.ClientFactory = mockClientFactory(mock)
|
|
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = tt.cmd.Run(tt.root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTasksAddCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmd *TasksAddCmd
|
|
mockResp []byte
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful add task",
|
|
cmd: &TasksAddCmd{Title: "New Task"},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"id": "task-new-123",
|
|
"title": "New Task",
|
|
}),
|
|
wantInOut: "Task created",
|
|
},
|
|
{
|
|
name: "add task with all options",
|
|
cmd: &TasksAddCmd{
|
|
Title: "Full Task",
|
|
ListID: "list-123",
|
|
Due: "2024-01-20",
|
|
Notes: "Task notes",
|
|
Important: true,
|
|
},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"id": "task-new-456",
|
|
"title": "Full Task",
|
|
}),
|
|
wantInOut: "Task created",
|
|
},
|
|
{
|
|
name: "add task with tomorrow due",
|
|
cmd: &TasksAddCmd{Title: "Tomorrow Task", Due: "tomorrow"},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"id": "task-new-789",
|
|
"title": "Tomorrow Task",
|
|
}),
|
|
wantInOut: "Task created",
|
|
},
|
|
{
|
|
name: "add task with today due",
|
|
cmd: &TasksAddCmd{Title: "Today Task", Due: "today"},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"id": "task-new-789",
|
|
"title": "Today Task",
|
|
}),
|
|
wantInOut: "Task created",
|
|
},
|
|
{
|
|
name: "API error",
|
|
cmd: &TasksAddCmd{Title: "Task"},
|
|
mockErr: errors.New("API error"),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
callCount := 0
|
|
mock := &testutil.MockClient{
|
|
GetFunc: func(ctx context.Context, path string, query url.Values) ([]byte, error) {
|
|
return mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "list-default-123", "displayName": "Tasks"},
|
|
},
|
|
}), nil
|
|
},
|
|
PostFunc: func(ctx context.Context, path string, body interface{}) ([]byte, error) {
|
|
callCount++
|
|
return tt.mockResp, tt.mockErr
|
|
},
|
|
}
|
|
root := &Root{ClientFactory: mockClientFactory(mock)}
|
|
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = tt.cmd.Run(root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTasksUpdateCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmd *TasksUpdateCmd
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful update title",
|
|
cmd: &TasksUpdateCmd{
|
|
TaskID: "task-123",
|
|
ListID: "list-123",
|
|
Title: "Updated Title",
|
|
},
|
|
wantInOut: "Task updated",
|
|
},
|
|
{
|
|
name: "update all fields",
|
|
cmd: &TasksUpdateCmd{
|
|
TaskID: "task-123",
|
|
ListID: "list-123",
|
|
Title: "Updated",
|
|
Due: "2024-01-25",
|
|
Notes: "New notes",
|
|
Important: true,
|
|
},
|
|
wantInOut: "Task updated",
|
|
},
|
|
{
|
|
name: "no list ID specified",
|
|
cmd: &TasksUpdateCmd{
|
|
TaskID: "task-123",
|
|
Title: "Update",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "no updates specified",
|
|
cmd: &TasksUpdateCmd{
|
|
TaskID: "task-123",
|
|
ListID: "list-123",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "API error",
|
|
cmd: &TasksUpdateCmd{
|
|
TaskID: "task-123",
|
|
ListID: "list-123",
|
|
Title: "Update",
|
|
},
|
|
mockErr: errors.New("API error"),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mock := &testutil.MockClient{
|
|
PatchFunc: func(ctx context.Context, path string, body interface{}) ([]byte, error) {
|
|
return []byte(`{}`), tt.mockErr
|
|
},
|
|
}
|
|
root := &Root{ClientFactory: mockClientFactory(mock)}
|
|
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = tt.cmd.Run(root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTasksDoneCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmd *TasksDoneCmd
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful mark done",
|
|
cmd: &TasksDoneCmd{TaskID: "task-123", ListID: "list-123"},
|
|
wantInOut: "Task completed",
|
|
},
|
|
{
|
|
name: "no list ID specified",
|
|
cmd: &TasksDoneCmd{TaskID: "task-123"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "API error",
|
|
cmd: &TasksDoneCmd{TaskID: "task-123", ListID: "list-123"},
|
|
mockErr: errors.New("API error"),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mock := &testutil.MockClient{
|
|
PatchFunc: func(ctx context.Context, path string, body interface{}) ([]byte, error) {
|
|
return []byte(`{}`), tt.mockErr
|
|
},
|
|
}
|
|
root := &Root{ClientFactory: mockClientFactory(mock)}
|
|
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = tt.cmd.Run(root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTasksUndoCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmd *TasksUndoCmd
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful mark undone",
|
|
cmd: &TasksUndoCmd{TaskID: "task-123", ListID: "list-123"},
|
|
wantInOut: "Task uncompleted",
|
|
},
|
|
{
|
|
name: "no list ID specified",
|
|
cmd: &TasksUndoCmd{TaskID: "task-123"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "API error",
|
|
cmd: &TasksUndoCmd{TaskID: "task-123", ListID: "list-123"},
|
|
mockErr: errors.New("API error"),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mock := &testutil.MockClient{
|
|
PatchFunc: func(ctx context.Context, path string, body interface{}) ([]byte, error) {
|
|
return []byte(`{}`), tt.mockErr
|
|
},
|
|
}
|
|
root := &Root{ClientFactory: mockClientFactory(mock)}
|
|
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = tt.cmd.Run(root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTasksDeleteCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmd *TasksDeleteCmd
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful delete",
|
|
cmd: &TasksDeleteCmd{TaskID: "task-123", ListID: "list-123"},
|
|
wantInOut: "Task deleted",
|
|
},
|
|
{
|
|
name: "no list ID specified",
|
|
cmd: &TasksDeleteCmd{TaskID: "task-123"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "API error",
|
|
cmd: &TasksDeleteCmd{TaskID: "task-123", ListID: "list-123"},
|
|
mockErr: errors.New("API error"),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mock := &testutil.MockClient{
|
|
DeleteFunc: func(ctx context.Context, path string) error {
|
|
return tt.mockErr
|
|
},
|
|
}
|
|
root := &Root{ClientFactory: mockClientFactory(mock)}
|
|
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = tt.cmd.Run(root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTasksClearCmd_Run(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmd *TasksClearCmd
|
|
mockResp []byte
|
|
mockErr error
|
|
wantErr bool
|
|
wantInOut string
|
|
}{
|
|
{
|
|
name: "successful clear completed",
|
|
cmd: &TasksClearCmd{ListID: "list-123"},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "task-1", "title": "Completed 1", "status": "completed"},
|
|
{"id": "task-2", "title": "Completed 2", "status": "completed"},
|
|
},
|
|
}),
|
|
wantInOut: "Cleared 2 completed tasks",
|
|
},
|
|
{
|
|
name: "clear without list ID uses default",
|
|
cmd: &TasksClearCmd{},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "task-1", "title": "Completed", "status": "completed"},
|
|
},
|
|
}),
|
|
wantInOut: "Cleared 1 completed tasks",
|
|
},
|
|
{
|
|
name: "no completed tasks",
|
|
cmd: &TasksClearCmd{ListID: "list-123"},
|
|
mockResp: mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{},
|
|
}),
|
|
wantInOut: "Cleared 0 completed tasks",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mock := &testutil.MockClient{
|
|
GetFunc: func(ctx context.Context, path string, query url.Values) ([]byte, error) {
|
|
if tt.mockErr != nil {
|
|
return nil, tt.mockErr
|
|
}
|
|
// Return task lists for default list lookup
|
|
if path == "/me/todo/lists" {
|
|
return mustJSON(map[string]interface{}{
|
|
"value": []map[string]interface{}{
|
|
{"id": "list-default-123", "displayName": "Tasks"},
|
|
},
|
|
}), nil
|
|
}
|
|
return tt.mockResp, nil
|
|
},
|
|
DeleteFunc: func(ctx context.Context, path string) error {
|
|
return nil
|
|
},
|
|
}
|
|
root := &Root{ClientFactory: mockClientFactory(mock)}
|
|
|
|
var output string
|
|
err := error(nil)
|
|
output = captureOutput(func() {
|
|
err = tt.cmd.Run(root)
|
|
})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
if tt.wantInOut != "" {
|
|
assert.Contains(t, output, tt.wantInOut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test type unmarshaling
|
|
func TestTaskList_Unmarshal(t *testing.T) {
|
|
jsonData := `{
|
|
"id": "list-123",
|
|
"displayName": "My Tasks",
|
|
"isOwner": true
|
|
}`
|
|
|
|
var list TaskList
|
|
err := json.Unmarshal([]byte(jsonData), &list)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "list-123", list.ID)
|
|
assert.Equal(t, "My Tasks", list.DisplayName)
|
|
assert.True(t, list.IsOwner)
|
|
}
|
|
|
|
func TestTask_Unmarshal(t *testing.T) {
|
|
jsonData := `{
|
|
"id": "task-123",
|
|
"title": "Complete project",
|
|
"status": "notStarted",
|
|
"importance": "high",
|
|
"dueDateTime": {
|
|
"dateTime": "2024-01-20T00:00:00",
|
|
"timeZone": "UTC"
|
|
},
|
|
"body": {
|
|
"content": "Task notes",
|
|
"contentType": "text"
|
|
}
|
|
}`
|
|
|
|
var task Task
|
|
err := json.Unmarshal([]byte(jsonData), &task)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "task-123", task.ID)
|
|
assert.Equal(t, "Complete project", task.Title)
|
|
assert.Equal(t, "notStarted", task.Status)
|
|
assert.Equal(t, "high", task.Importance)
|
|
assert.NotNil(t, task.DueDateTime)
|
|
assert.Equal(t, "2024-01-20T00:00:00", task.DueDateTime.DateTime)
|
|
assert.NotNil(t, task.Body)
|
|
assert.Equal(t, "Task notes", task.Body.Content)
|
|
}
|