diff --git a/shortcuts/task/shortcuts.go b/shortcuts/task/shortcuts.go index 4112ebd847..fa8058f208 100644 --- a/shortcuts/task/shortcuts.go +++ b/shortcuts/task/shortcuts.go @@ -247,6 +247,7 @@ var CreateTask = common.Shortcut{ "guid": guid, "url": urlVal, } + projectTaskFields(outData, task, standardTaskOutputFields...) runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task created successfully!\n") diff --git a/shortcuts/task/task_assign.go b/shortcuts/task/task_assign.go index d0134a5189..11dc93d7f8 100644 --- a/shortcuts/task/task_assign.go +++ b/shortcuts/task/task_assign.go @@ -91,6 +91,7 @@ var AssignTask = common.Shortcut{ "guid": taskId, "url": urlVal, } + projectTaskFields(outData, task, standardTaskOutputFields...) runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task assignes updated successfully!\n") diff --git a/shortcuts/task/task_complete.go b/shortcuts/task/task_complete.go index 9b46886572..2f49f6c2d7 100644 --- a/shortcuts/task/task_complete.go +++ b/shortcuts/task/task_complete.go @@ -101,6 +101,7 @@ var CompleteTask = common.Shortcut{ "completed_at": completedAt, "already_completed": alreadyCompleted, } + projectTaskFields(outData, task, taskOutputSummary, taskOutputMembers, taskOutputStart, taskOutputDue) runtime.OutFormat(outData, nil, func(w io.Writer) { summary, _ := task["summary"].(string) diff --git a/shortcuts/task/task_complete_test.go b/shortcuts/task/task_complete_test.go index 7394324bcb..c4cb498a32 100644 --- a/shortcuts/task/task_complete_test.go +++ b/shortcuts/task/task_complete_test.go @@ -51,6 +51,10 @@ func TestCompleteTask(t *testing.T) { `"status": "done"`, `"completed_at": "1775174400000"`, `"already_completed": false`, + `"summary": "Test Task task-789"`, + `"members":`, + `"start":`, + `"due":`, }, }, } @@ -76,6 +80,9 @@ func TestCompleteTask(t *testing.T) { "summary": "Test Task " + tt.taskId, "completed_at": completedAt, "url": "https://example.com/" + tt.taskId, + "members": fullTaskOutputFixture()["members"], + "start": fullTaskOutputFixture()["start"], + "due": fullTaskOutputFixture()["due"], }, }, }, @@ -93,6 +100,9 @@ func TestCompleteTask(t *testing.T) { "summary": "Test Task " + tt.taskId, "completed_at": "1775174400000", "url": "https://example.com/" + tt.taskId, + "members": fullTaskOutputFixture()["members"], + "start": fullTaskOutputFixture()["start"], + "due": fullTaskOutputFixture()["due"], }, }, }, diff --git a/shortcuts/task/task_followers.go b/shortcuts/task/task_followers.go index 3016ad1106..9ea4853363 100644 --- a/shortcuts/task/task_followers.go +++ b/shortcuts/task/task_followers.go @@ -92,6 +92,7 @@ var FollowersTask = common.Shortcut{ "guid": taskId, "url": urlVal, } + projectTaskFields(outData, task, standardTaskOutputFields...) runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task followers updated successfully!\n") diff --git a/shortcuts/task/task_get_my_tasks.go b/shortcuts/task/task_get_my_tasks.go index 334430cac5..30283a3816 100644 --- a/shortcuts/task/task_get_my_tasks.go +++ b/shortcuts/task/task_get_my_tasks.go @@ -222,6 +222,7 @@ var GetMyTasks = common.Shortcut{ } } } + projectTaskFields(outputItem, item, taskOutputMembers, taskOutputStart) outputItems = append(outputItems, outputItem) } diff --git a/shortcuts/task/task_get_my_tasks_test.go b/shortcuts/task/task_get_my_tasks_test.go index 69d650439d..223dab47de 100644 --- a/shortcuts/task/task_get_my_tasks_test.go +++ b/shortcuts/task/task_get_my_tasks_test.go @@ -45,6 +45,8 @@ func TestGetMyTasks_LocalTimeFormatting(t *testing.T) { expectedOutput: []string{ `"due_at": "` + expectedRFC3339 + `"`, `"created_at": "` + expectedRFC3339 + `"`, + `"role": "assignee"`, + `"start": {`, }, }, { @@ -74,6 +76,13 @@ func TestGetMyTasks_LocalTimeFormatting(t *testing.T) { "guid": "task-123", "summary": "Test Task", "created_at": tsStr, + "members": []interface{}{ + map[string]interface{}{"id": "ou_owner", "role": "assignee", "type": "user"}, + }, + "start": map[string]interface{}{ + "timestamp": tsStr, + "is_all_day": false, + }, "due": map[string]interface{}{ "timestamp": tsStr, }, diff --git a/shortcuts/task/task_get_related_tasks_test.go b/shortcuts/task/task_get_related_tasks_test.go index 18d9de7860..2d7eb0a7a1 100644 --- a/shortcuts/task/task_get_related_tasks_test.go +++ b/shortcuts/task/task_get_related_tasks_test.go @@ -123,13 +123,15 @@ func TestGetRelatedTasks_Execute(t *testing.T) { "tasklists": []interface{}{}, "url": "https://example.com/task-123", "creator": map[string]interface{}{"id": "ou_testuser", "type": "user"}, + "start": map[string]interface{}{"timestamp": "1775088000000", "is_all_day": false}, + "due": map[string]interface{}{"timestamp": "1775174400000", "is_all_day": false}, }, }, }, }, }) }, - wantParts: []string{`"guid": "task-123"`, `"summary": "Related Task"`}, + wantParts: []string{`"guid": "task-123"`, `"summary": "Related Task"`, `"start":`, `"due":`}, }, { name: "pretty pagination followed by me", diff --git a/shortcuts/task/task_output.go b/shortcuts/task/task_output.go new file mode 100644 index 0000000000..a74568d328 --- /dev/null +++ b/shortcuts/task/task_output.go @@ -0,0 +1,31 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package task + +type taskOutputField string + +const ( + taskOutputSummary taskOutputField = "summary" + taskOutputMembers taskOutputField = "members" + taskOutputStart taskOutputField = "start" + taskOutputDue taskOutputField = "due" + taskOutputStatus taskOutputField = "status" +) + +var standardTaskOutputFields = []taskOutputField{ + taskOutputSummary, + taskOutputMembers, + taskOutputStart, + taskOutputDue, + taskOutputStatus, +} + +func projectTaskFields(dst, task map[string]interface{}, fields ...taskOutputField) { + for _, field := range fields { + key := string(field) + if value, ok := task[key]; ok { + dst[key] = value + } + } +} diff --git a/shortcuts/task/task_output_test.go b/shortcuts/task/task_output_test.go new file mode 100644 index 0000000000..6a2d9ffef4 --- /dev/null +++ b/shortcuts/task/task_output_test.go @@ -0,0 +1,144 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package task + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" + + "github.com/larksuite/cli/internal/httpmock" + "github.com/larksuite/cli/shortcuts/common" +) + +func TestProjectTaskFields(t *testing.T) { + task := map[string]interface{}{ + "summary": "Ship compact fields", + "members": []interface{}{map[string]interface{}{ + "id": "ou_owner", "name": "Owner", "type": "user", "role": "assignee", + }}, + "start": map[string]interface{}{"timestamp": "1000", "is_all_day": false}, + "due": map[string]interface{}{"timestamp": "2000", "is_all_day": false}, + "status": "todo", + } + out := map[string]interface{}{"guid": "task-1"} + + projectTaskFields(out, task, standardTaskOutputFields...) + + for _, field := range standardTaskOutputFields { + key := string(field) + if !reflect.DeepEqual(out[key], task[key]) { + t.Fatalf("%s = %#v, want %#v", key, out[key], task[key]) + } + } + if out["guid"] != "task-1" { + t.Fatalf("existing guid changed: %#v", out) + } +} + +func TestProjectTaskFieldsOmitsAbsentFields(t *testing.T) { + out := map[string]interface{}{} + projectTaskFields(out, map[string]interface{}{"summary": "Only title"}, standardTaskOutputFields...) + + if out["summary"] != "Only title" { + t.Fatalf("summary = %#v, want Only title", out["summary"]) + } + for _, key := range []string{"members", "start", "due", "status"} { + if _, ok := out[key]; ok { + t.Fatalf("%s unexpectedly projected: %#v", key, out) + } + } +} + +func TestTaskRootOutputFields(t *testing.T) { + tests := []struct { + name string + shortcut common.Shortcut + method string + url string + args []string + }{ + { + name: "create", shortcut: CreateTask, method: http.MethodPost, + url: "/open-apis/task/v2/tasks", + args: []string{"+create", "--summary", "Requested title", "--as", "bot", "--format", "json"}, + }, + { + name: "reopen", shortcut: ReopenTask, method: http.MethodPatch, + url: "/open-apis/task/v2/tasks/task-1", + args: []string{"+reopen", "--task-id", "task-1", "--as", "bot", "--format", "json"}, + }, + { + name: "assign", shortcut: AssignTask, method: http.MethodPost, + url: "/open-apis/task/v2/tasks/task-1/add_members", + args: []string{"+assign", "--task-id", "task-1", "--add", "ou_owner", "--as", "bot", "--format", "json"}, + }, + { + name: "followers", shortcut: FollowersTask, method: http.MethodPost, + url: "/open-apis/task/v2/tasks/task-1/add_members", + args: []string{"+followers", "--task-id", "task-1", "--add", "ou_follower", "--as", "bot", "--format", "json"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, stdout, _, reg := taskShortcutTestFactory(t) + warmTenantToken(t, f, reg) + reg.Register(&httpmock.Stub{ + Method: tt.method, + URL: tt.url, + Body: map[string]interface{}{ + "code": 0, + "msg": "success", + "data": map[string]interface{}{"task": fullTaskOutputFixture()}, + }, + }) + + shortcut := tt.shortcut + shortcut.AuthTypes = []string{"bot", "user"} + if err := runMountedTaskShortcut(t, shortcut, tt.args, f, stdout); err != nil { + t.Fatalf("runMountedTaskShortcut() error = %v", err) + } + + assertStandardTaskFields(t, decodeTaskOutputData(t, stdout.Bytes())) + }) + } +} + +func fullTaskOutputFixture() map[string]interface{} { + return map[string]interface{}{ + "guid": "task-1", + "url": "https://example.com/task-1&suite_entity_num=t1", + "summary": "Server title", + "members": []interface{}{ + map[string]interface{}{"id": "ou_owner", "name": "Owner", "type": "user", "role": "assignee"}, + }, + "start": map[string]interface{}{"timestamp": "1000", "is_all_day": false}, + "due": map[string]interface{}{"timestamp": "2000", "is_all_day": false}, + "status": "todo", + } +} + +func decodeTaskOutputData(t *testing.T, raw []byte) map[string]interface{} { + t.Helper() + var envelope map[string]interface{} + if err := json.Unmarshal(raw, &envelope); err != nil { + t.Fatalf("decode output: %v\n%s", err, raw) + } + data, ok := envelope["data"].(map[string]interface{}) + if !ok { + t.Fatalf("data = %#v, want object", envelope["data"]) + } + return data +} + +func assertStandardTaskFields(t *testing.T, data map[string]interface{}) { + t.Helper() + for _, key := range []string{"summary", "members", "start", "due", "status"} { + if _, ok := data[key]; !ok { + t.Errorf("data missing %q: %#v", key, data) + } + } +} diff --git a/shortcuts/task/task_query_helpers.go b/shortcuts/task/task_query_helpers.go index eba2906ef2..99e5dfd7d6 100644 --- a/shortcuts/task/task_query_helpers.go +++ b/shortcuts/task/task_query_helpers.go @@ -164,6 +164,7 @@ func outputTaskSummary(task map[string]interface{}) map[string]interface{} { } } } + projectTaskFields(out, task, taskOutputMembers, taskOutputStart, taskOutputStatus) return out } @@ -198,6 +199,7 @@ func outputRelatedTask(task map[string]interface{}) map[string]interface{} { out["completed_at"] = completed } } + projectTaskFields(out, task, taskOutputStart, taskOutputDue) return out } diff --git a/shortcuts/task/task_query_helpers_test.go b/shortcuts/task/task_query_helpers_test.go index 09e00ae9d7..dfd2519099 100644 --- a/shortcuts/task/task_query_helpers_test.go +++ b/shortcuts/task/task_query_helpers_test.go @@ -5,6 +5,7 @@ package task import ( "errors" + "reflect" "strings" "testing" @@ -74,6 +75,11 @@ func TestOutputTaskSummary(t *testing.T) { "summary": "summary", "url": "https://example.com/task-123&suite_entity_num=t1", "created_at": "1775174400000", + "members": []interface{}{ + map[string]interface{}{"id": "ou_owner", "role": "assignee", "type": "user"}, + }, + "start": map[string]interface{}{"timestamp": "1775088000000", "is_all_day": false}, + "status": "todo", "due": map[string]interface{}{ "timestamp": "1775174400000", }, @@ -100,6 +106,15 @@ func TestOutputTaskSummary(t *testing.T) { if got["url"] == "" { t.Fatalf("expected url in output, got %#v", got) } + if tt.name == "with timestamps and due" { + if !reflect.DeepEqual(got["members"], tt.task["members"]) || + !reflect.DeepEqual(got["start"], tt.task["start"]) || got["status"] != "todo" { + t.Fatalf("search projection lost compact fields: %#v", got) + } + if _, duplicated := got["due"]; duplicated { + t.Fatalf("search projection duplicated due alongside due_at: %#v", got) + } + } }) } } @@ -200,6 +215,8 @@ func TestOutputRelatedTaskAndTimeRangeFilter(t *testing.T) { "url": "https://example.com/task-123&suite_entity_num=t1", "creator": map[string]interface{}{"id": "ou_1"}, "members": []interface{}{map[string]interface{}{"id": "ou_2", "role": "follower"}}, + "start": map[string]interface{}{"timestamp": "1775088000000", "is_all_day": false}, + "due": map[string]interface{}{"timestamp": "1775174400000", "is_all_day": false}, "created_at": "1775174400000", "completed_at": "1775174400000", }, @@ -219,6 +236,10 @@ func TestOutputRelatedTaskAndTimeRangeFilter(t *testing.T) { if got["guid"] != tt.task["guid"] || got["summary"] != tt.task["summary"] { t.Fatalf("unexpected related task output: %#v", got) } + if tt.name == "full related task" && + (!reflect.DeepEqual(got["start"], tt.task["start"]) || !reflect.DeepEqual(got["due"], tt.task["due"])) { + t.Fatalf("related task projection lost start/due: %#v", got) + } }) } diff --git a/shortcuts/task/task_reminder.go b/shortcuts/task/task_reminder.go index 029ab21c6d..6286d1ecda 100644 --- a/shortcuts/task/task_reminder.go +++ b/shortcuts/task/task_reminder.go @@ -77,7 +77,9 @@ var ReminderTask = common.Shortcut{ if runtime.Bool("remove") { if len(reminders) == 0 { - runtime.OutFormat(map[string]interface{}{"guid": taskId}, nil, func(w io.Writer) { + outData := map[string]interface{}{"guid": taskId} + projectTaskFields(outData, taskObj, standardTaskOutputFields...) + runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintln(w, "No existing reminders to remove.") }) return nil @@ -169,6 +171,7 @@ var ReminderTask = common.Shortcut{ "guid": taskId, "url": urlVal, } + projectTaskFields(outData, taskObj, standardTaskOutputFields...) runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task reminders updated successfully!\n") diff --git a/shortcuts/task/task_reminder_test.go b/shortcuts/task/task_reminder_test.go index 0d8319c063..1459d14191 100644 --- a/shortcuts/task/task_reminder_test.go +++ b/shortcuts/task/task_reminder_test.go @@ -4,10 +4,12 @@ package task import ( + "encoding/json" "errors" "testing" "github.com/larksuite/cli/errs" + "github.com/larksuite/cli/internal/httpmock" "github.com/larksuite/cli/internal/output" ) @@ -53,3 +55,35 @@ func TestReminderTask_CannotSpecifyBoth(t *testing.T) { t.Errorf("exit code = %d, want %d", got, output.ExitValidation) } } + +func TestReminderTask_NoExistingReminderReturnsTaskFields(t *testing.T) { + f, stdout, _, reg := taskShortcutTestFactory(t) + warmTenantToken(t, f, reg) + + task := fullTaskOutputFixture() + task["reminders"] = []interface{}{} + reg.Register(&httpmock.Stub{ + Method: "GET", + URL: "/open-apis/task/v2/tasks/task-1", + Body: map[string]interface{}{ + "code": 0, + "msg": "success", + "data": map[string]interface{}{"task": task}, + }, + }) + + shortcut := ReminderTask + shortcut.AuthTypes = []string{"bot", "user"} + if err := runMountedTaskShortcut(t, shortcut, []string{ + "+reminder", "--task-id", "task-1", "--remove", "--as", "bot", "--format", "json", + }, f, stdout); err != nil { + t.Fatalf("ReminderTask error = %v", err) + } + + var envelope map[string]interface{} + if err := json.Unmarshal(stdout.Bytes(), &envelope); err != nil { + t.Fatalf("decode output: %v\n%s", err, stdout.String()) + } + data, _ := envelope["data"].(map[string]interface{}) + assertStandardTaskFields(t, data) +} diff --git a/shortcuts/task/task_reopen.go b/shortcuts/task/task_reopen.go index 0790ee0d8e..bf7bde6c28 100644 --- a/shortcuts/task/task_reopen.go +++ b/shortcuts/task/task_reopen.go @@ -55,6 +55,7 @@ var ReopenTask = common.Shortcut{ "guid": guid, "url": urlVal, } + projectTaskFields(outData, task, standardTaskOutputFields...) runtime.OutFormat(outData, nil, func(w io.Writer) { summary, _ := task["summary"].(string) diff --git a/shortcuts/task/task_update.go b/shortcuts/task/task_update.go index 00cf7f8c48..fed2c1cdbc 100644 --- a/shortcuts/task/task_update.go +++ b/shortcuts/task/task_update.go @@ -97,11 +97,13 @@ var UpdateTask = common.Shortcut{ confirmed[field] = value } } - tasks = append(tasks, map[string]interface{}{ + item := map[string]interface{}{ "guid": guid, "url": urlVal, "confirmed": confirmed, - }) + } + projectTaskFields(item, task, standardTaskOutputFields...) + tasks = append(tasks, item) } // Standardized write output: return resource identifiers outData := map[string]interface{}{ diff --git a/shortcuts/task/task_update_test.go b/shortcuts/task/task_update_test.go index 396477db7d..ea29e617f5 100644 --- a/shortcuts/task/task_update_test.go +++ b/shortcuts/task/task_update_test.go @@ -84,20 +84,22 @@ func TestTaskUpdateDryRunPreviewsEveryTaskID(t *testing.T) { func TestTaskUpdateNormalizesAllIDsAndReturnsConfirmedFields(t *testing.T) { f, stdout, _, reg := taskShortcutTestFactory(t) warmTenantToken(t, f, reg) + firstTaskResponse := fullTaskOutputFixture() + firstTaskResponse["guid"] = "task-guid-1" + firstTaskResponse["url"] = "https://example.com/task-guid-1" + firstTaskResponse["summary"] = "server summary one" + firstTaskResponse["description"] = "server description one" + secondTaskResponse := fullTaskOutputFixture() + secondTaskResponse["guid"] = "task-guid-2" + secondTaskResponse["url"] = "https://example.com/task-guid-2" + secondTaskResponse["summary"] = "server summary two" first := &httpmock.Stub{ Method: "PATCH", URL: "/open-apis/task/v2/tasks/task-guid-1", Body: map[string]interface{}{ "code": 0, "msg": "success", - "data": map[string]interface{}{ - "task": map[string]interface{}{ - "guid": "task-guid-1", - "url": "https://example.com/task-guid-1", - "summary": "server summary one", - "description": "server description one", - }, - }, + "data": map[string]interface{}{"task": firstTaskResponse}, }, } second := &httpmock.Stub{ @@ -105,13 +107,7 @@ func TestTaskUpdateNormalizesAllIDsAndReturnsConfirmedFields(t *testing.T) { URL: "/open-apis/task/v2/tasks/task-guid-2", Body: map[string]interface{}{ "code": 0, "msg": "success", - "data": map[string]interface{}{ - "task": map[string]interface{}{ - "guid": "task-guid-2", - "url": "https://example.com/task-guid-2", - "summary": "server summary two", - }, - }, + "data": map[string]interface{}{"task": secondTaskResponse}, }, } reg.Register(first) @@ -155,6 +151,7 @@ func TestTaskUpdateNormalizesAllIDsAndReturnsConfirmedFields(t *testing.T) { }) { t.Fatalf("first confirmed = %#v", got) } + assertStandardTaskFields(t, firstTask) secondTask := tasks[1].(map[string]interface{}) if got := secondTask["confirmed"]; !reflect.DeepEqual(got, map[string]interface{}{ @@ -162,6 +159,7 @@ func TestTaskUpdateNormalizesAllIDsAndReturnsConfirmedFields(t *testing.T) { }) { t.Fatalf("second confirmed = %#v; omitted server fields must not be echoed from the request", got) } + assertStandardTaskFields(t, secondTask) } func TestTaskUpdateValidatesEveryIDBeforeFirstWrite(t *testing.T) { diff --git a/shortcuts/task/tasklist_add_task.go b/shortcuts/task/tasklist_add_task.go index 6f868b7ed3..65410595b8 100644 --- a/shortcuts/task/tasklist_add_task.go +++ b/shortcuts/task/tasklist_add_task.go @@ -91,10 +91,12 @@ var AddTaskToTasklist = common.Shortcut{ guid, _ := task["guid"].(string) taskUrl, _ := task["url"].(string) taskUrl = truncateTaskURL(taskUrl) - successful = append(successful, map[string]interface{}{ + item := map[string]interface{}{ "guid": guid, "url": taskUrl, - }) + } + projectTaskFields(item, task, standardTaskOutputFields...) + successful = append(successful, item) } } diff --git a/shortcuts/task/tasklist_add_task_test.go b/shortcuts/task/tasklist_add_task_test.go index 5a19c6d23f..832457bb57 100644 --- a/shortcuts/task/tasklist_add_task_test.go +++ b/shortcuts/task/tasklist_add_task_test.go @@ -4,6 +4,7 @@ package task import ( + "encoding/json" "errors" "strings" "testing" @@ -16,17 +17,14 @@ import ( func TestAddTaskToTasklist_Success(t *testing.T) { f, stdout, _, reg := taskShortcutTestFactory(t) warmTenantToken(t, f, reg) + task := fullTaskOutputFixture() reg.Register(&httpmock.Stub{ Method: "POST", URL: "/open-apis/task/v2/tasks/task-1/add_tasklist", Body: map[string]interface{}{ "code": 0, "msg": "success", - "data": map[string]interface{}{ - "task": map[string]interface{}{ - "guid": "task-1", - }, - }, + "data": map[string]interface{}{"task": task}, }, }) @@ -43,6 +41,16 @@ func TestAddTaskToTasklist_Success(t *testing.T) { if !strings.Contains(out, `"tasklist_guid":"tl-123"`) && !strings.Contains(out, `"tasklist_guid": "tl-123"`) { t.Errorf("expected tasklist_guid in output, got: %s", out) } + var envelope map[string]interface{} + if decodeErr := json.Unmarshal(stdout.Bytes(), &envelope); decodeErr != nil { + t.Fatalf("decode output: %v\n%s", decodeErr, out) + } + data, _ := envelope["data"].(map[string]interface{}) + successful, _ := data["successful_tasks"].([]interface{}) + if len(successful) != 1 { + t.Fatalf("successful_tasks = %#v, want one item", data["successful_tasks"]) + } + assertStandardTaskFields(t, successful[0].(map[string]interface{})) } // TestAddTaskToTasklist_PartialFailure exercises the batch path: some tasks diff --git a/shortcuts/task/tasklist_create.go b/shortcuts/task/tasklist_create.go index 0923bba492..471a11a1a0 100644 --- a/shortcuts/task/tasklist_create.go +++ b/shortcuts/task/tasklist_create.go @@ -123,10 +123,12 @@ var CreateTasklist = common.Shortcut{ guid, _ := t["guid"].(string) urlVal, _ := t["url"].(string) urlVal = truncateTaskURL(urlVal) - createdTasks = append(createdTasks, map[string]interface{}{ + item := map[string]interface{}{ "guid": guid, "url": urlVal, - }) + } + projectTaskFields(item, t, standardTaskOutputFields...) + createdTasks = append(createdTasks, item) } }(i, taskDef) } diff --git a/shortcuts/task/tasklist_create_test.go b/shortcuts/task/tasklist_create_test.go index 16abe4b7cd..09c9fe1a69 100644 --- a/shortcuts/task/tasklist_create_test.go +++ b/shortcuts/task/tasklist_create_test.go @@ -5,6 +5,7 @@ package task import ( "bytes" + "encoding/json" "errors" "strings" "testing" @@ -25,6 +26,9 @@ import ( func TestCreateTasklist_PartialFailure(t *testing.T) { f, stdout, _, reg := taskShortcutTestFactory(t) warmTenantToken(t, f, reg) + createdTask := fullTaskOutputFixture() + createdTask["guid"] = "task-ok" + createdTask["url"] = "https://example.feishu.cn/task-ok" reg.Register(&httpmock.Stub{ Method: "POST", @@ -48,12 +52,7 @@ func TestCreateTasklist_PartialFailure(t *testing.T) { BodyFilter: func(b []byte) bool { return bytes.Contains(b, []byte("ok-task")) }, Body: map[string]interface{}{ "code": 0, "msg": "success", - "data": map[string]interface{}{ - "task": map[string]interface{}{ - "guid": "task-ok", - "url": "https://example.feishu.cn/task-ok", - }, - }, + "data": map[string]interface{}{"task": createdTask}, }, }) @@ -105,6 +104,16 @@ func TestCreateTasklist_PartialFailure(t *testing.T) { if strings.Contains(out, "permission_error") { t.Errorf("legacy type \"permission_error\" leaked into output: %s", out) } + var envelope map[string]interface{} + if decodeErr := json.Unmarshal(stdout.Bytes(), &envelope); decodeErr != nil { + t.Fatalf("decode output: %v\n%s", decodeErr, out) + } + resultData, _ := envelope["data"].(map[string]interface{}) + createdTasks, _ := resultData["created_tasks"].([]interface{}) + if len(createdTasks) != 1 { + t.Fatalf("created_tasks = %#v, want one item", resultData["created_tasks"]) + } + assertStandardTaskFields(t, createdTasks[0].(map[string]interface{})) } func TestCreateTasklist_PartialFailurePrettyOutput(t *testing.T) {