Files
dingtalk-workspace-cli/pkg/asynctask/asynctask_test.go
539d10f80f feat: 合并 pre-mcp-discovery 分支能力到 main(服务发现保持生产) (#391)
将 test/pre-mcp-discovery 分支上多位同学的能力改动整体合入 main,
服务发现端点保持生产 https://mcp.dingtalk.com,未携带分支上的预发改动。

主要内容:
- 修雨域(chat / report / todo / contact / mail)对齐 wukong 基线 (#355)
- DWS 内容生产能力增强 (#387)、文档导出与 OA 对齐 (#362 #388 #390)
- envelope 注册 cli.Aliases 为 cobra aliases、命令结构与 JSON 解析优化
- skills 多包同步 wukong 对齐内容(attendance/calendar/minutes/oa/sheet 等)
- product/oa 进一步对齐 wukong

排除项:未携带 566b4e1(test: point service discovery to pre-mcp.dingtalk.com);
以下 4 个常量保持生产:skill_command.go / auth/endpoints.go / cli/loader.go / market/registry.go。

Co-authored-by: john <32427341+wqyenjoy@users.noreply.github.com>
Co-authored-by: wxianfeng <wang.fl1429@gmail.com>
2026-06-02 21:04:12 +08:00

164 lines
5.5 KiB
Go

// Copyright 2026 Alibaba Group
// SPDX-License-Identifier: Apache-2.0
package asynctask
import (
"context"
"errors"
"testing"
"time"
)
// 用极小 backoff 加速测试
var testBackoff = []time.Duration{1 * time.Millisecond, 2 * time.Millisecond, 5 * time.Millisecond}
func TestSubmit_HappyPath(t *testing.T) {
submitFn := func(ctx context.Context) (string, error) { return "job-1", nil }
calls := 0
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) {
calls++
if calls < 3 {
return QueryResult{Status: StatusProcessing}, nil
}
return QueryResult{Status: StatusSuccess, DownloadURL: "https://example/x.docx"}, nil
}
res, err := Submit(context.Background(), submitFn, queryFn, Options{
Backoff: testBackoff,
Timeout: 1 * time.Second,
})
if err != nil {
t.Fatalf("err: %v", err)
}
if res.Status != StatusSuccess || res.DownloadURL != "https://example/x.docx" {
t.Fatalf("got: %+v", res)
}
if res.Attempts != 3 {
t.Fatalf("attempts: %d", res.Attempts)
}
}
func TestSubmit_Failed(t *testing.T) {
submitFn := func(ctx context.Context) (string, error) { return "job-2", nil }
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) {
return QueryResult{Status: StatusFailed, Message: "INVALID_NODE"}, nil
}
res, _ := Submit(context.Background(), submitFn, queryFn, Options{Backoff: testBackoff, Timeout: 1 * time.Second})
if res.Status != StatusFailed || res.Message != "INVALID_NODE" {
t.Fatalf("got: %+v", res)
}
}
func TestSubmit_Timeout(t *testing.T) {
submitFn := func(ctx context.Context) (string, error) { return "job-3", nil }
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) {
return QueryResult{Status: StatusProcessing}, nil
}
res, err := Submit(context.Background(), submitFn, queryFn, Options{
Backoff: testBackoff,
Timeout: 10 * time.Millisecond, // 故意很短
})
if err != nil {
t.Fatalf("err: %v", err)
}
if res.Status != StatusTimeout {
t.Fatalf("expect TIMEOUT, got: %+v", res)
}
if res.JobID != "job-3" {
t.Fatalf("job id lost: %s", res.JobID)
}
}
func TestSubmit_QueryRetry(t *testing.T) {
// 模拟单次 query 失败但下次成功的场景:本包应该忽略单次失败继续轮询
submitFn := func(ctx context.Context) (string, error) { return "job-4", nil }
calls := 0
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) {
calls++
if calls == 1 {
return QueryResult{}, errors.New("transient 502")
}
return QueryResult{Status: StatusSuccess, DownloadURL: "ok"}, nil
}
res, err := Submit(context.Background(), submitFn, queryFn, Options{Backoff: testBackoff, Timeout: 1 * time.Second})
if err != nil || res.Status != StatusSuccess {
t.Fatalf("transient retry: %+v err=%v", res, err)
}
}
func TestSubmit_SubmitError(t *testing.T) {
submitFn := func(ctx context.Context) (string, error) { return "", errors.New("auth fail") }
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) { return QueryResult{}, nil }
_, err := Submit(context.Background(), submitFn, queryFn, Options{Backoff: testBackoff, Timeout: 1 * time.Second})
if err == nil {
t.Fatal("expected submit error")
}
}
func TestResume_AlreadyHasJobID(t *testing.T) {
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) {
if jobID != "preexisting-job" {
t.Fatalf("jobID lost: %s", jobID)
}
return QueryResult{Status: StatusSuccess, DownloadURL: "ok"}, nil
}
res, err := Resume(context.Background(), "preexisting-job", queryFn, Options{Backoff: testBackoff, Timeout: 1 * time.Second})
if err != nil || res.Status != StatusSuccess {
t.Fatalf("resume: %+v err=%v", res, err)
}
}
func TestSubmit_ContextCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
submitFn := func(ctx context.Context) (string, error) { return "job-x", nil }
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) { return QueryResult{Status: StatusProcessing}, nil }
_, err := Submit(ctx, submitFn, queryFn, Options{Backoff: testBackoff, Timeout: 1 * time.Second})
if !errors.Is(err, context.Canceled) {
t.Fatalf("expect ctx.Canceled, got %v", err)
}
}
func TestSubmit_ProgressCallback(t *testing.T) {
submitFn := func(ctx context.Context) (string, error) { return "job-5", nil }
calls := 0
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) {
calls++
if calls < 2 {
return QueryResult{Status: StatusProcessing}, nil
}
return QueryResult{Status: StatusSuccess}, nil
}
var seenStatuses []Status
progress := func(attempt int, status Status, elapsed time.Duration) {
seenStatuses = append(seenStatuses, status)
}
_, _ = Submit(context.Background(), submitFn, queryFn, Options{
Backoff: testBackoff,
Timeout: 1 * time.Second,
ProgressFn: progress,
})
if len(seenStatuses) < 2 {
t.Fatalf("progress not called enough: %v", seenStatuses)
}
if seenStatuses[len(seenStatuses)-1] != StatusSuccess {
t.Fatalf("final status not seen: %v", seenStatuses)
}
}
func TestSubmit_UnknownStatusKeepsPolling(t *testing.T) {
submitFn := func(ctx context.Context) (string, error) { return "job-6", nil }
calls := 0
queryFn := func(ctx context.Context, jobID string) (QueryResult, error) {
calls++
if calls < 3 {
return QueryResult{Status: "WEIRD_STATUS"}, nil
}
return QueryResult{Status: StatusSuccess}, nil
}
res, _ := Submit(context.Background(), submitFn, queryFn, Options{Backoff: testBackoff, Timeout: 1 * time.Second})
if res.Status != StatusSuccess {
t.Fatalf("unknown status not handled: %+v", res)
}
}