Files
dingtalk-workspace-cli/internal/shortcut/smart/minutes_completion_coverage_test.go
T

409 lines
21 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0
package smart
import (
"bytes"
"errors"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/localio"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
"github.com/spf13/cobra"
)
type smartMinutesFailWriter struct{}
func (smartMinutesFailWriter) Write([]byte) (int, error) {
return 0, errors.New("fixture output failure")
}
func runMinutesCLIWithWriter(t *testing.T, caller *smartCoverageCaller, writer io.Writer, args ...string) error {
t.Helper()
helpers.InitDepsForTest(t, caller)
root := newPlatformCoverageRoot()
root.SetOut(writer)
root.SetErr(&bytes.Buffer{})
root.SetArgs(args)
return root.Execute()
}
func TestCrossPlatformCoverageMinutesSmartContractFinalizer(t *testing.T) {
value := shortcut.Shortcut{
Description: "desc", Intent: "intent",
Contract: corecmd.ContractDecl{Parameters: []contract.ParamDecl{
{Name: "a", Description: "parameter"},
{Name: "a", Description: "evidence present"},
{Name: "other", Description: "other"},
}},
Flags: []shortcut.Flag{{Name: "a", Desc: "flag"}},
Constraints: []shortcut.Constraint{
{Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"a"}},
{Kind: shortcut.ConstraintCustom, Flags: []string{"a"}},
{Kind: shortcut.ConstraintCustom, Flags: []string{"a"}, Description: "evidence"},
},
}
got := finalizeMinutesSmartShortcut(value)
if got.Contract.Selection.AgentSummary != "desc" || got.Contract.Selection.UseWhen[0] != "intent" || !strings.Contains(got.Flags[0].Desc, "evidence") || !strings.Contains(got.Contract.Parameters[0].Description, "evidence") || got.Contract.Parameters[1].Description != "evidence present" || got.Contract.Parameters[2].Description != "other" {
t.Fatalf("finalized=%#v", got)
}
}
func TestCrossPlatformCoverageMinutesLatestAndActionItemsBranchesE2E(t *testing.T) {
listKey := "minutes/list_by_keyword_and_time_range"
basicKey := "minutes/get_minutes_basic_info"
todoKey := "minutes/list_minutes_todos"
latestCases := []struct {
name string
responses map[string][]string
failAt map[string]int
wantError bool
}{
{name: "list call", failAt: map[string]int{listKey: 1}, wantError: true},
{name: "list parse", responses: map[string][]string{listKey: {`{"success":true,"result":{}}`}}, wantError: true},
{name: "empty", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[]}}`}}, wantError: true},
{name: "basic call", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"uuid":"u1","startTime":1}]}}`}}, failAt: map[string]int{basicKey: 1}, wantError: true},
{name: "basic parse", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"uuid":"u1","startTime":1}]}}`}, basicKey: {`{"success":true,"result":{}}`}}, wantError: true},
{name: "success", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"uuid":"u1","startTime":1}]}}`}, basicKey: {`{"success":true,"result":{"taskUuid":"u1","title":"ok"}}`}}},
}
for _, test := range latestCases {
t.Run("latest "+test.name, func(t *testing.T) {
payload, _, err := runMinutesCLI(t, &smartCoverageCaller{responses: test.responses, failAt: test.failAt}, "minutes", "+latest", "--keyword", "weekly")
if (err != nil) != test.wantError {
t.Fatalf("payload=%#v err=%v", payload, err)
}
})
}
latestOutput := &smartCoverageCaller{responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"uuid":"u1","startTime":1}]}}`}, basicKey: {`{"success":true,"result":{"taskUuid":"u1"}}`}}}
if err := runMinutesCLIWithWriter(t, latestOutput, smartMinutesFailWriter{}, "minutes", "+latest"); err == nil {
t.Fatal("latest output failure accepted")
}
if _, err := latestMinutesTaskUUID(map[string]any{}); err == nil {
t.Fatal("latest malformed data accepted")
}
actionCases := []struct {
name string
responses map[string][]string
failAt map[string]int
args []string
wantError bool
}{
{name: "list call", failAt: map[string]int{listKey: 1}, wantError: true},
{name: "list parse", responses: map[string][]string{listKey: {`{"success":true,"result":{}}`}}, wantError: true},
{name: "empty", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[]}}`}}, wantError: true},
{name: "todo call", failAt: map[string]int{todoKey: 1}, args: []string{"--id", "u1"}, wantError: true},
{name: "todo parse", responses: map[string][]string{todoKey: {`{"success":true,"result":{}}`}}, args: []string{"--id", "u1"}, wantError: true},
{name: "success id", responses: map[string][]string{todoKey: {`{"success":true,"result":{"actions":[]}}`}}, args: []string{"--id", "u1"}},
{name: "success latest", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"uuid":"u1","startTime":1}]}}`}, todoKey: {`{"success":true,"result":{"actions":[]}}`}}},
}
for _, test := range actionCases {
t.Run("actions "+test.name, func(t *testing.T) {
args := []string{"minutes", "+action-items"}
args = append(args, test.args...)
payload, _, err := runMinutesCLI(t, &smartCoverageCaller{responses: test.responses, failAt: test.failAt}, args...)
if (err != nil) != test.wantError {
t.Fatalf("payload=%#v err=%v", payload, err)
}
})
}
actionOutput := &smartCoverageCaller{responses: map[string][]string{todoKey: {`{"success":true,"result":{"actions":[]}}`}}}
if err := runMinutesCLIWithWriter(t, actionOutput, smartMinutesFailWriter{}, "minutes", "+action-items", "--id", "u1"); err == nil {
t.Fatal("actions output failure accepted")
}
}
func TestCrossPlatformCoverageMinutesSearchAndTranscriptBranchesE2E(t *testing.T) {
listKey := "minutes/list_by_keyword_and_time_range"
transcriptKey := "minutes/get_minutes_transcription"
searchCases := []struct {
name string
responses map[string][]string
failAt map[string]int
}{
{name: "call", failAt: map[string]int{listKey: 1}},
{name: "project", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"title":"missing id"}]}}`}}},
}
for _, test := range searchCases {
if _, _, err := runMinutesCLI(t, &smartCoverageCaller{responses: test.responses, failAt: test.failAt}, "minutes", "+minutes-search", "--query", "x"); err == nil {
t.Fatalf("search %s failure accepted", test.name)
}
}
searchOutput := &smartCoverageCaller{responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[]}}`}}}
if err := runMinutesCLIWithWriter(t, searchOutput, smartMinutesFailWriter{}, "minutes", "+minutes-search", "--query", "x"); err == nil {
t.Fatal("search output failure accepted")
}
if _, _, err := runMinutesCLI(t, &smartCoverageCaller{}, "minutes", "+transcript", "--id", "u1", "--page-limit", "0"); err == nil {
t.Fatal("transcript page limit accepted")
}
transcriptCases := []struct {
name string
responses map[string][]string
failAt map[string]int
wantError bool
}{
{name: "list call", failAt: map[string]int{listKey: 1}, wantError: true},
{name: "list parse", responses: map[string][]string{listKey: {`{"success":true,"result":{}}`}}, wantError: true},
{name: "empty", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[]}}`}}, wantError: true},
{name: "success", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"uuid":"u1","startTime":1}]}}`}, transcriptKey: {`{"success":true,"result":{"paragraphList":[{"paragraphId":"p1"}],"hasNext":false}}`}}},
{name: "partial", responses: map[string][]string{listKey: {`{"success":true,"result":{"itemList":[{"uuid":"u1","startTime":1}]}}`}, transcriptKey: {`{"success":true,"result":{"paragraphList":[{"paragraphId":"p1"}],"hasNext":true,"nextToken":"n2"}}`}}, failAt: map[string]int{transcriptKey: 2}, wantError: true},
}
for _, test := range transcriptCases {
t.Run("transcript "+test.name, func(t *testing.T) {
payload, output, err := runMinutesCLI(t, &smartCoverageCaller{responses: test.responses, failAt: test.failAt}, "minutes", "+transcript", "--keyword", "weekly", "--direction", "1")
if (err != nil) != test.wantError {
t.Fatalf("payload=%#v output=%q err=%v", payload, output, err)
}
})
}
transcriptOutput := &smartCoverageCaller{responses: map[string][]string{transcriptKey: {`{"success":true,"result":{"paragraphList":[],"hasNext":false}}`}}}
if err := runMinutesCLIWithWriter(t, transcriptOutput, smartMinutesFailWriter{}, "minutes", "+transcript", "--id", "u1"); err == nil {
t.Fatal("transcript output failure accepted")
}
}
func TestCrossPlatformCoverageMinutesDetailRemainingBranchesE2E(t *testing.T) {
invalid := [][]string{
{"minutes", "+detail", "--id", "u1", "--page-limit", "0"},
{"minutes", "+detail", "--id", "u1", "--ids", "u2"},
{"minutes", "+detail"},
{"minutes", "+detail", "--id", "u1", "--transcript-output", "file", "--output-dir", "../escape"},
}
for _, args := range invalid {
if payload, output, err := runMinutesCLI(t, &smartCoverageCaller{}, args...); err == nil || payload != nil || output != "" {
t.Fatalf("detail invalid accepted %v payload=%#v output=%q err=%v", args, payload, output, err)
}
}
all := &smartCoverageCaller{responses: map[string][]string{
"minutes/get_minutes_basic_info": {`{"success":true,"result":{"taskUuid":"u1"}}`},
"minutes/get_minutes_ai_summary": {`{"success":true,"result":{"fullSummary":"summary"}}`},
"minutes/get_minutes_keywords": {`{"success":true,"result":{"keywords":[]}}`},
"minutes/get_minutes_transcription": {`{"success":true,"result":{"paragraphList":[{"paragraphId":"p1"}],"hasNext":false}}`},
"minutes/list_minutes_todos": {`{"success":true,"result":{"actions":[]}}`},
}}
payload, _, err := runMinutesCLI(t, all, "minutes", "+detail", "--id", "u1")
if err != nil || payload["complete"] != true {
t.Fatalf("detail all payload=%#v err=%v", payload, err)
}
if err := runMinutesCLIWithWriter(t, all, smartMinutesFailWriter{}, "minutes", "+detail", "--id", "u1", "--artifacts", "basic"); err == nil {
t.Fatal("detail single output failure accepted")
}
batchOutput := &smartCoverageCaller{responses: map[string][]string{"minutes/get_minutes_basic_info": {`{"success":true,"result":{"taskUuid":"u1"}}`, `{"success":true,"result":{"taskUuid":"u2"}}`}}}
if err := runMinutesCLIWithWriter(t, batchOutput, smartMinutesFailWriter{}, "minutes", "+detail", "--ids", "u1,u2", "--artifacts", "basic"); err == nil {
t.Fatal("detail batch output failure accepted")
}
for _, artifact := range []string{"basic", "summary", "keywords", "todos"} {
tool := minutesArtifactTools[artifact]
caller := &smartCoverageCaller{failAt: map[string]int{"minutes/" + tool: 1}}
payload, output, err := runMinutesCLI(t, caller, "minutes", "+detail", "--id", "u1", "--artifacts", artifact)
if err == nil || output == "" || payload["complete"] != false {
t.Fatalf("detail call %s payload=%#v err=%v", artifact, payload, err)
}
caller = &smartCoverageCaller{responses: map[string][]string{"minutes/" + tool: {`{"success":true,"result":{}}`}}}
payload, output, err = runMinutesCLI(t, caller, "minutes", "+detail", "--id", "u1", "--artifacts", artifact)
if err == nil || output == "" || payload["complete"] != false {
t.Fatalf("detail parse %s payload=%#v err=%v", artifact, payload, err)
}
}
transcript := &smartCoverageCaller{responses: map[string][]string{"minutes/get_minutes_transcription": {`{"success":true,"result":{"paragraphList":[{"paragraphId":"p1"}],"hasNext":false}}`}}}
testseam.Swap(t, &smartMinutesMarshalIndent, func(any, string, string) ([]byte, error) { return nil, errors.New("marshal") })
payload, output, err := runMinutesCLI(t, transcript, "minutes", "+detail", "--id", "u1", "--artifacts", "transcript", "--transcript-output", "file", "--output-dir", "out")
if err == nil || output == "" || payload["complete"] != false {
t.Fatalf("detail marshal payload=%#v err=%v", payload, err)
}
}
func TestCrossPlatformCoverageMinutesDetailPublishFailure(t *testing.T) {
testseam.Swap(t, &smartMinutesPublishBytes, func([]byte, localio.PublishBytesOptions) (localio.DownloadResult, error) {
return localio.DownloadResult{}, errors.New("publish")
})
caller := &smartCoverageCaller{responses: map[string][]string{"minutes/get_minutes_transcription": {`{"success":true,"result":{"paragraphList":[{"paragraphId":"p1"}],"hasNext":false}}`}}}
payload, output, err := runMinutesCLI(t, caller, "minutes", "+detail", "--id", "u1", "--artifacts", "transcript", "--transcript-output", "both", "--output-dir", "out")
if err == nil || output == "" || payload["complete"] != false {
t.Fatalf("detail publish payload=%#v err=%v", payload, err)
}
}
func TestCrossPlatformCoverageMinutesDetailFileSuccess(t *testing.T) {
old, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
work := t.TempDir()
if err := os.Chdir(work); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chdir(old) })
caller := &smartCoverageCaller{responses: map[string][]string{"minutes/get_minutes_transcription": {`{"success":true,"result":{"paragraphList":[{"paragraphId":"p1"}],"hasNext":false}}`}}}
payload, _, err := runMinutesCLI(t, caller, "minutes", "+detail", "--id", "u1", "--artifacts", "transcript", "--transcript-output", "file", "--output-dir", "out")
if err != nil || payload["complete"] != true {
t.Fatalf("detail file payload=%#v err=%v", payload, err)
}
if _, statErr := os.Stat(filepath.Join(work, "out", "u1.json")); statErr != nil {
t.Fatal(statErr)
}
helpers.InitDepsForTest(t, caller)
cmd := &cobra.Command{Use: "detail"}
cmd.Flags().String("cursor", "", "")
cmd.Flags().Bool("single-page", false, "")
cmd.Flags().Int("page-limit", 100, "")
cmd.Flags().String("transcript-output", "inline", "")
rt := shortcut.RuntimeContextForTest(cmd, MinutesDetail)
if bundle, failures := readMinutesDetail(rt, "u1", []string{"unknown"}, "0"); len(failures) != 0 || bundle["complete"] != true {
t.Fatalf("unknown detail bundle=%#v failures=%#v", bundle, failures)
}
}
func TestCrossPlatformCoverageMinutesReplaceParsing(t *testing.T) {
for _, raw := range [][]string{{"missing"}, {" =>x"}, {"a=>x", "a=>y"}} {
if pairs, err := parseReplacePairs(raw); err == nil || pairs != nil {
t.Fatalf("invalid pairs accepted: %#v pairs=%#v err=%v", raw, pairs, err)
}
}
if pairs, err := parseReplacePairs([]string{" a => b "}); err != nil || len(pairs) != 1 || pairs[0].orig != "a" || pairs[0].repl != "b" {
t.Fatalf("pairs=%#v err=%v", pairs, err)
}
for _, raw := range []string{"not-json", `[]`, `[{"replacement":"x"}]`} {
if pairs, err := parseReplaceJSON(raw); err == nil || pairs != nil {
t.Fatalf("invalid JSON accepted: %q pairs=%#v err=%v", raw, pairs, err)
}
}
if pairs, err := parseReplaceJSON(`{"replacements":[{"originalText":" a ","replacedText":" b "}]}`); err != nil || len(pairs) != 1 || pairs[0] != (replacePair{orig: "a", repl: "b"}) {
t.Fatalf("envelope pairs=%#v err=%v", pairs, err)
}
if pairs, err := parseReplaceJSON(`[{"source":"a","replacement":"b"}]`); err != nil || len(pairs) != 1 {
t.Fatalf("direct pairs=%#v err=%v", pairs, err)
}
for _, pairs := range [][]replacePair{nil, {{orig: " "}}, {{orig: "a"}, {orig: "a"}}} {
if got, err := validateReplacePairs(pairs); err == nil || got != nil {
t.Fatalf("invalid validated: %#v got=%#v err=%v", pairs, got, err)
}
}
valid := []replacePair{{orig: "a", repl: "b"}}
if got, err := validateReplacePairs(valid); err != nil || len(got) != 1 {
t.Fatalf("valid=%#v err=%v", got, err)
}
for _, test := range []struct {
before, after string
pair replacePair
beforeSource int
beforeTarget int
want bool
}{
{before: "same", after: "same", pair: replacePair{orig: "a", repl: "b"}},
{before: "a", after: "", pair: replacePair{orig: "a"}, beforeSource: 1, want: true},
{before: "a", after: "a", pair: replacePair{orig: "a"}, beforeSource: 1},
{before: "a", after: "aa", pair: replacePair{orig: "a", repl: "aa"}, beforeSource: 1, beforeTarget: 0, want: true},
{before: "a", after: "b", pair: replacePair{orig: "a", repl: "b"}, beforeSource: 1, beforeTarget: 0, want: true},
{before: "a", after: "c", pair: replacePair{orig: "a", repl: "b"}, beforeSource: 1, beforeTarget: 0},
} {
if got := replaceReadbackVerified(test.before, test.after, test.pair, test.beforeSource, test.beforeTarget); got != test.want {
t.Fatalf("verified(%#v)=%v", test, got)
}
}
}
func TestCrossPlatformCoverageMinutesReplaceLoadAndTranscript(t *testing.T) {
cmd := &cobra.Command{Use: "replace"}
cmd.Flags().StringSlice("pair", []string{"bad"}, "")
cmd.Flags().String("json", "", "")
if pairs, err := loadReplacePairs(shortcut.RuntimeContextForTest(cmd, ReplaceBatch)); err == nil || pairs != nil {
t.Fatalf("direct malformed pair accepted pairs=%#v err=%v", pairs, err)
}
if _, _, err := runMinutesCLI(t, &smartCoverageCaller{}, "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>b", "--page-limit", "0"); err == nil {
t.Fatal("replace page limit accepted")
}
if _, _, err := runMinutesCLI(t, &smartCoverageCaller{}, "minutes", "+replace-batch", "--id", "u1", "--json", "@missing", "--dry-run"); err == nil {
t.Fatal("missing JSON file accepted")
}
if _, _, err := runMinutesCLI(t, &smartCoverageCaller{}, "minutes", "+replace-batch", "--id", "u1", "--json", "bad", "--dry-run"); err == nil {
t.Fatal("malformed JSON accepted")
}
if _, _, err := runMinutesCLI(t, &smartCoverageCaller{}, "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>b", "--json", `[{"source":"a","replacement":"c"}]`, "--dry-run"); err == nil {
t.Fatal("cross-input duplicate accepted")
}
for _, test := range []struct {
name string
responses []string
failAt int
}{
{name: "call", failAt: 1},
{name: "empty", responses: []string{`{"success":true,"result":{"paragraphList":[],"hasNext":false}}`}},
{name: "no text", responses: []string{`{"success":true,"result":{"paragraphList":[{"paragraphId":"p1"}],"hasNext":false}}`}},
} {
t.Run(test.name, func(t *testing.T) {
caller := &smartCoverageCaller{responses: map[string][]string{"minutes/get_minutes_transcription": test.responses}, failAt: map[string]int{"minutes/get_minutes_transcription": test.failAt}}
helpers.InitDepsForTest(t, caller)
cmd := &cobra.Command{Use: "replace"}
rt := shortcut.RuntimeContextForTest(cmd, ReplaceBatch)
if text, err := minutesReplaceTranscriptText(rt, "u1", 1); err == nil || text != "" {
t.Fatalf("text=%q err=%v", text, err)
}
})
}
nested := &smartCoverageCaller{responses: map[string][]string{"minutes/get_minutes_transcription": {`{"success":true,"result":{"paragraphList":[{"paragraph":"one","sentences":[{"text":"two"}]}],"hasNext":false}}`}}}
helpers.InitDepsForTest(t, nested)
if text, err := minutesReplaceTranscriptText(shortcut.RuntimeContextForTest(&cobra.Command{Use: "replace"}, ReplaceBatch), "u1", 1); err != nil || !strings.Contains(text, "one") || !strings.Contains(text, "two") {
t.Fatalf("nested text=%q err=%v", text, err)
}
}
func TestCrossPlatformCoverageMinutesReplaceExecutionBranchesE2E(t *testing.T) {
transcriptKey := "minutes/get_minutes_transcription"
writeKey := "minutes/replace_minutes_text"
preRead := &smartCoverageCaller{failAt: map[string]int{transcriptKey: 1}}
if _, _, err := runMinutesCLI(t, preRead, "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>A", "--yes"); err == nil {
t.Fatal("replace pre-read failure accepted")
}
missing := &smartCoverageCaller{responses: map[string][]string{transcriptKey: {`{"success":true,"result":{"paragraphList":[{"paragraph":"other"}],"hasNext":false}}`}}}
if _, _, err := runMinutesCLI(t, missing, "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>A", "--yes"); err == nil || missing.counts[writeKey] != 0 {
t.Fatal("missing source write accepted")
}
readback := &smartCoverageCaller{
responses: map[string][]string{
transcriptKey: {`{"success":true,"result":{"paragraphList":[{"paragraph":"a"}],"hasNext":false}}`},
writeKey: {`{"success":true,"result":{}}`},
},
failAt: map[string]int{transcriptKey: 2},
}
payload, output, err := runMinutesCLI(t, readback, "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>A", "--yes")
if err == nil || output == "" || payload["failed"] != float64(1) {
t.Fatalf("readback payload=%#v err=%v", payload, err)
}
success := func() *smartCoverageCaller {
return &smartCoverageCaller{responses: map[string][]string{
transcriptKey: {
`{"success":true,"result":{"paragraphList":[{"paragraph":"a"}],"hasNext":false}}`,
`{"success":true,"result":{"paragraphList":[{"paragraph":"A"}],"hasNext":false}}`,
},
writeKey: {`{"success":true,"result":{}}`},
}}
}
payload, output, err = runMinutesCLI(t, success(), "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>A", "--yes")
if err != nil || output == "" || payload["ok"] != true || payload["applied"] != float64(1) {
t.Fatalf("success payload=%#v err=%v", payload, err)
}
if err := runMinutesCLIWithWriter(t, success(), smartMinutesFailWriter{}, "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>A", "--yes"); err == nil {
t.Fatal("replace output failure accepted")
}
if err := runMinutesCLIWithWriter(t, &smartCoverageCaller{}, smartMinutesFailWriter{}, "minutes", "+replace-batch", "--id", "u1", "--pair", "a=>A", "--dry-run"); err == nil {
t.Fatal("replace dry-run output failure accepted")
}
}