bf9395e022
CI / results (push) Blocked by required conditions
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / e2e-live (push) Waiting to run
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / deadcode (push) Waiting to run
119 lines
3.8 KiB
Go
119 lines
3.8 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package task
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
"github.com/larksuite/cli/internal/httpmock"
|
|
"github.com/larksuite/cli/internal/output"
|
|
)
|
|
|
|
func TestAddTaskToTasklist_Success(t *testing.T) {
|
|
f, stdout, _, reg := taskShortcutTestFactory(t)
|
|
warmTenantToken(t, f, reg)
|
|
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
s := AddTaskToTasklist
|
|
s.AuthTypes = []string{"bot", "user"}
|
|
|
|
args := []string{"+tasklist-task-add", "--tasklist-id", "tl-123", "--task-id", "task-1", "--section-guid", "sec-456", "--as", "bot", "--format", "json"}
|
|
err := runMountedTaskShortcut(t, s, args, f, stdout)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
out := stdout.String()
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestAddTaskToTasklist_PartialFailure exercises the batch path: some tasks
|
|
// succeed, others fail with typed API errors. Successful and failed tasks both
|
|
// land in stdout as an ok:false envelope, and the command returns the typed
|
|
// partial-failure exit signal (exit 1) via runtime.OutPartialFailure. The
|
|
// failed_tasks[].type carries the typed subtype (e.g. "permission_denied",
|
|
// "not_found") read off errs.ProblemOf.
|
|
func TestAddTaskToTasklist_PartialFailure(t *testing.T) {
|
|
f, stdout, _, reg := taskShortcutTestFactory(t)
|
|
warmTenantToken(t, f, reg)
|
|
|
|
reg.Register(&httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/task/v2/tasks/task-ok/add_tasklist",
|
|
Body: map[string]interface{}{
|
|
"code": 0, "msg": "success",
|
|
"data": map[string]interface{}{
|
|
"task": map[string]interface{}{
|
|
"guid": "task-ok",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
reg.Register(&httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/task/v2/tasks/task-perm/add_tasklist",
|
|
Body: map[string]interface{}{
|
|
"code": ErrCodeTaskPermissionDenied, "msg": "no permission",
|
|
},
|
|
})
|
|
reg.Register(&httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/task/v2/tasks/task-missing/add_tasklist",
|
|
Body: map[string]interface{}{
|
|
"code": ErrCodeTaskNotFound, "msg": "task not found",
|
|
},
|
|
})
|
|
|
|
s := AddTaskToTasklist
|
|
s.AuthTypes = []string{"bot", "user"}
|
|
|
|
args := []string{"+tasklist-task-add", "--tasklist-id", "tl-123", "--task-id", "task-ok,task-perm,task-missing", "--as", "bot", "--format", "json"}
|
|
err := runMountedTaskShortcut(t, s, args, f, stdout)
|
|
// Partial failure now surfaces as a non-zero exit (ok:false), not nil.
|
|
var pfErr *output.PartialFailureError
|
|
if !errors.As(err, &pfErr) {
|
|
t.Fatalf("expected *output.PartialFailureError on partial failure, got %T: %v", err, err)
|
|
}
|
|
if pfErr.Code != output.ExitAPI {
|
|
t.Errorf("exit code = %d, want %d (ExitAPI)", pfErr.Code, output.ExitAPI)
|
|
}
|
|
|
|
out := stdout.String()
|
|
|
|
// Successful task is in stdout.
|
|
if !strings.Contains(out, "task-ok") {
|
|
t.Errorf("expected successful task-ok in output, got: %s", out)
|
|
}
|
|
|
|
// Failed tasks carry the typed subtype, not the legacy Detail.Type.
|
|
if !strings.Contains(out, string(errs.SubtypePermissionDenied)) {
|
|
t.Errorf("expected typed subtype %q in failed_tasks, got: %s", errs.SubtypePermissionDenied, out)
|
|
}
|
|
if !strings.Contains(out, string(errs.SubtypeNotFound)) {
|
|
t.Errorf("expected typed subtype %q in failed_tasks, got: %s", errs.SubtypeNotFound, out)
|
|
}
|
|
|
|
// The legacy shapes must not leak.
|
|
if strings.Contains(out, "permission_error") {
|
|
t.Errorf("legacy type \"permission_error\" leaked into output: %s", out)
|
|
}
|
|
}
|