@@ -10,8 +10,8 @@ import (
1010 "testing"
1111 "time"
1212
13+ "github.com/larksuite/cli/errs"
1314 "github.com/larksuite/cli/internal/core"
14- "github.com/larksuite/cli/internal/output"
1515)
1616
1717// pollWikiAsyncTask is shared infrastructure for every wiki delete shortcut,
@@ -88,69 +88,78 @@ func TestPollWikiAsyncTaskAllPollsFailWrapsWithResumeHint(t *testing.T) {
8888 t .Parallel ()
8989
9090 runtime , stderr := newWikiNodeDeleteRuntime (t , core .AsUser )
91+ transportErr := errors .New ("transport boom" )
9192 _ , ready , err := pollWikiAsyncTask (
9293 context .Background (), runtime , "task_lost" , "delete-node" , 2 , 0 ,
9394 func (context.Context , string ) (wikiAsyncTaskStatus , error ) {
94- return wikiAsyncTaskStatus {}, errors . New ( "transport boom" )
95+ return wikiAsyncTaskStatus {}, transportErr
9596 },
9697 "lark-cli drive +task_result --task-id task_lost" ,
9798 )
9899 if ready {
99100 t .Fatalf ("ready = true, want false when every poll failed" )
100101 }
101- var exitErr * output. ExitError
102- if ! errors . As ( err , & exitErr ) || exitErr . Detail == nil {
103- t .Fatalf ("err = %T %v, want *output.ExitError with detail " , err , err )
102+ p , ok := errs . ProblemOf ( err )
103+ if ! ok {
104+ t .Fatalf ("err = %T %v, want a typed errs.* error " , err , err )
104105 }
105- if exitErr . Code != output . ExitAPI {
106- t .Fatalf ("exit code = %d , want ExitAPI " , exitErr . Code )
106+ if p . Subtype != errs . SubtypeUnknown {
107+ t .Fatalf ("subtype = %q , want unknown for an untyped poll failure " , p . Subtype )
107108 }
108- if ! strings .Contains (exitErr .Detail .Hint , "every status poll failed (task_id=task_lost)" ) ||
109- ! strings .Contains (exitErr .Detail .Hint , "lark-cli drive +task_result --task-id task_lost" ) {
110- t .Fatalf ("hint = %q, want resume guidance naming the task" , exitErr .Detail .Hint )
109+ if ! errors .Is (err , transportErr ) {
110+ t .Fatalf ("err does not preserve the transport cause: %v" , err )
111+ }
112+ if ! strings .Contains (p .Hint , "every status poll failed (task_id=task_lost)" ) ||
113+ ! strings .Contains (p .Hint , "lark-cli drive +task_result --task-id task_lost" ) {
114+ t .Fatalf ("hint = %q, want resume guidance naming the task" , p .Hint )
111115 }
112116 if ! strings .Contains (stderr .String (), "attempt 2/2 failed" ) {
113117 t .Fatalf ("stderr = %q, want per-attempt progress" , stderr .String ())
114118 }
115119}
116120
121+ func TestParseWikiAsyncTaskStatusRejectsNilTask (t * testing.T ) {
122+ t .Parallel ()
123+
124+ _ , err := parseWikiAsyncTaskStatus ("task_x" , nil , "delete_space_result" )
125+ p , ok := errs .ProblemOf (err )
126+ if ! ok || p .Category != errs .CategoryInternal || p .Subtype != errs .SubtypeInvalidResponse {
127+ t .Fatalf ("expected internal/invalid_response, got %v" , err )
128+ }
129+ }
130+
117131func TestPollWikiAsyncTaskPrependsUpstreamExitHint (t * testing.T ) {
118132 t .Parallel ()
119133
120134 runtime , _ := newWikiNodeDeleteRuntime (t , core .AsUser )
121- upstream := & output.ExitError {
122- Code : output .ExitAPI ,
123- Detail : & output.ErrDetail {
124- Type : "permission" ,
125- Code : 99991663 ,
126- Message : "permission denied" ,
127- Hint : "grant the wiki:node:retrieve scope" ,
128- },
129- }
135+ // The upstream poll error is a typed error carrying its own hint, mirroring
136+ // what runtime.CallAPITyped produces for a permission failure.
137+ upstream := errs .NewPermissionError (errs .SubtypePermissionDenied , "permission denied" ).
138+ WithHint ("grant the wiki:node:retrieve scope" )
130139 _ , _ , err := pollWikiAsyncTask (
131140 context .Background (), runtime , "task_perm" , "delete-node" , 1 , 0 ,
132141 func (context.Context , string ) (wikiAsyncTaskStatus , error ) {
133142 return wikiAsyncTaskStatus {}, upstream
134143 },
135144 "resume-cmd" ,
136145 )
137- var exitErr * output. ExitError
138- if ! errors . As ( err , & exitErr ) || exitErr . Detail == nil {
139- t .Fatalf ("err = %T %v, want *output.ExitError " , err , err )
146+ p , ok := errs . ProblemOf ( err )
147+ if ! ok {
148+ t .Fatalf ("err = %T %v, want a typed errs.* error " , err , err )
140149 }
141150 // The upstream hint must lead so the actionable cause is read first, with
142- // the resume guidance appended. Type and exit code propagate from upstream .
143- if ! strings .HasPrefix (exitErr . Detail .Hint , "grant the wiki:node:retrieve scope\n " ) {
144- t .Fatalf ("hint = %q, want upstream hint prepended" , exitErr . Detail .Hint )
151+ // the resume guidance appended. The original typed error propagates in place .
152+ if ! strings .HasPrefix (p .Hint , "grant the wiki:node:retrieve scope\n " ) {
153+ t .Fatalf ("hint = %q, want upstream hint prepended" , p .Hint )
145154 }
146- if ! strings .Contains (exitErr . Detail .Hint , "resume-cmd" ) {
147- t .Fatalf ("hint = %q, want resume command appended" , exitErr . Detail .Hint )
155+ if ! strings .Contains (p .Hint , "resume-cmd" ) {
156+ t .Fatalf ("hint = %q, want resume command appended" , p .Hint )
148157 }
149- if exitErr . Detail . Type != "permission" || exitErr . Code != output . ExitAPI {
150- t .Fatalf ("exitErr = %+v , want permission/ExitAPI propagated" , exitErr )
158+ if p . Subtype != errs . SubtypePermissionDenied {
159+ t .Fatalf ("subtype = %q , want permission_denied propagated" , p . Subtype )
151160 }
152- if exitErr . Detail .Message != "permission denied" {
153- t .Fatalf ("message = %q, want upstream message preserved" , exitErr . Detail .Message )
161+ if p .Message != "permission denied" {
162+ t .Fatalf ("message = %q, want upstream message preserved" , p .Message )
154163 }
155164}
156165
0 commit comments