Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5ecab7b

Browse files
authored
chore(cli): add single CRUD-style test for tasks (#20385)
Adds a single CRUD-style test for tasks CLI using a single `coderdtest` instance.
1 parentdf3b1bb commit5ecab7b

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed

‎cli/exp_task_test.go‎

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,240 @@ package cli_test
22

33
import (
44
"context"
5+
"encoding/json"
56
"net/http"
67
"net/http/httptest"
8+
"slices"
9+
"strings"
710
"sync"
811
"testing"
12+
"time"
913

1014
"github.com/google/uuid"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
"golang.org/x/xerrors"
18+
19+
agentapisdk"github.com/coder/agentapi-sdk-go"
1120

1221
"github.com/coder/coder/v2/agent"
1322
"github.com/coder/coder/v2/agent/agenttest"
23+
"github.com/coder/coder/v2/cli/clitest"
1424
"github.com/coder/coder/v2/coderd/coderdtest"
25+
"github.com/coder/coder/v2/coderd/util/ptr"
1526
"github.com/coder/coder/v2/codersdk"
1627
"github.com/coder/coder/v2/codersdk/agentsdk"
1728
"github.com/coder/coder/v2/provisioner/echo"
1829
"github.com/coder/coder/v2/provisionersdk/proto"
30+
"github.com/coder/coder/v2/testutil"
1931
)
2032

33+
// This test performs an integration-style test for tasks functionality.
34+
//
35+
//nolint:tparallel // The sub-tests of this test must be run sequentially.
36+
funcTest_Tasks(t*testing.T) {
37+
t.Parallel()
38+
39+
// Given: a template configured for tasks
40+
var (
41+
ctx=testutil.Context(t,testutil.WaitLong)
42+
client=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerDaemon:true})
43+
owner=coderdtest.CreateFirstUser(t,client)
44+
userClient,_=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID)
45+
initMsg= agentapisdk.Message{
46+
Content:"test task input for "+t.Name(),
47+
Id:0,
48+
Role:"user",
49+
Time:time.Now().UTC(),
50+
}
51+
authToken=uuid.NewString()
52+
echoAgentAPI=startFakeAgentAPI(t,fakeAgentAPIEcho(ctx,t,initMsg,"hello"))
53+
taskTpl=createAITaskTemplate(t,client,owner.OrganizationID,withAgentToken(authToken),withSidebarURL(echoAgentAPI.URL()))
54+
taskName=strings.ReplaceAll(testutil.GetRandomName(t),"_","-")
55+
)
56+
57+
//nolint:paralleltest // The sub-tests of this test must be run sequentially.
58+
for_,tc:=range []struct {
59+
namestring
60+
cmdArgs []string
61+
assertFnfunc(stdoutstring,userClient*codersdk.Client)
62+
}{
63+
{
64+
name:"create task",
65+
cmdArgs: []string{"exp","task","create","test task input for "+t.Name(),"--name",taskName,"--template",taskTpl.Name},
66+
assertFn:func(stdoutstring,userClient*codersdk.Client) {
67+
require.Contains(t,stdout,taskName,"task name should be in output")
68+
},
69+
},
70+
{
71+
name:"list tasks after create",
72+
cmdArgs: []string{"exp","task","list","--output","json"},
73+
assertFn:func(stdoutstring,userClient*codersdk.Client) {
74+
vartasks []codersdk.Task
75+
err:=json.NewDecoder(strings.NewReader(stdout)).Decode(&tasks)
76+
require.NoError(t,err,"list output should unmarshal properly")
77+
require.Len(t,tasks,1,"expected one task")
78+
require.Equal(t,taskName,tasks[0].Name,"task name should match")
79+
require.Equal(t,initMsg.Content,tasks[0].InitialPrompt,"initial prompt should match")
80+
require.True(t,tasks[0].WorkspaceID.Valid,"workspace should be created")
81+
// For the next test, we need to wait for the workspace to be healthy
82+
ws:=coderdtest.MustWorkspace(t,userClient,tasks[0].WorkspaceID.UUID)
83+
coderdtest.AwaitWorkspaceBuildJobCompleted(t,client,ws.LatestBuild.ID)
84+
agentClient:=agentsdk.New(client.URL,agentsdk.WithFixedToken(authToken))
85+
_=agenttest.New(t,client.URL,authToken,func(o*agent.Options) {
86+
o.Client=agentClient
87+
})
88+
coderdtest.NewWorkspaceAgentWaiter(t,userClient,tasks[0].WorkspaceID.UUID).WithContext(ctx).WaitFor(coderdtest.AgentsReady)
89+
},
90+
},
91+
{
92+
name:"get task status after create",
93+
cmdArgs: []string{"exp","task","status",taskName,"--output","json"},
94+
assertFn:func(stdoutstring,userClient*codersdk.Client) {
95+
vartask codersdk.Task
96+
require.NoError(t,json.NewDecoder(strings.NewReader(stdout)).Decode(&task),"should unmarshal task status")
97+
require.Equal(t,task.Name,taskName,"task name should match")
98+
// NOTE: task status changes type, this is so this test works with both old and new model
99+
require.Contains(t, []string{"active","running"},string(task.Status),"task should be active")
100+
},
101+
},
102+
{
103+
name:"send task message",
104+
cmdArgs: []string{"exp","task","send",taskName,"hello"},
105+
// Assertions for this happen in the fake agent API handler.
106+
},
107+
{
108+
name:"read task logs",
109+
cmdArgs: []string{"exp","task","logs",taskName,"--output","json"},
110+
assertFn:func(stdoutstring,userClient*codersdk.Client) {
111+
varlogs []codersdk.TaskLogEntry
112+
require.NoError(t,json.NewDecoder(strings.NewReader(stdout)).Decode(&logs),"should unmarshal task logs")
113+
require.Len(t,logs,3,"should have 3 logs")
114+
require.Equal(t,logs[0].Content,initMsg.Content,"first message should be the init message")
115+
require.Equal(t,logs[0].Type,codersdk.TaskLogTypeInput,"first message should be an input")
116+
require.Equal(t,logs[1].Content,"hello","second message should be the sent message")
117+
require.Equal(t,logs[1].Type,codersdk.TaskLogTypeInput,"second message should be an input")
118+
require.Equal(t,logs[2].Content,"hello","third message should be the echoed message")
119+
require.Equal(t,logs[2].Type,codersdk.TaskLogTypeOutput,"third message should be an output")
120+
},
121+
},
122+
{
123+
name:"delete task",
124+
cmdArgs: []string{"exp","task","delete",taskName,"--yes"},
125+
assertFn:func(stdoutstring,userClient*codersdk.Client) {
126+
// The task should eventually no longer show up in the list of tasks
127+
testutil.Eventually(ctx,t,func(ctx context.Context)bool {
128+
expClient:=codersdk.NewExperimentalClient(userClient)
129+
tasks,err:=expClient.Tasks(ctx,&codersdk.TasksFilter{})
130+
if!assert.NoError(t,err) {
131+
returnfalse
132+
}
133+
returnslices.IndexFunc(tasks,func(task codersdk.Task)bool {
134+
returntask.Name==taskName
135+
})==-1
136+
},testutil.IntervalMedium)
137+
},
138+
},
139+
} {
140+
t.Run(tc.name,func(t*testing.T) {
141+
varstdout strings.Builder
142+
inv,root:=clitest.New(t,tc.cmdArgs...)
143+
inv.Stdout=&stdout
144+
clitest.SetupConfig(t,userClient,root)
145+
require.NoError(t,inv.WithContext(ctx).Run())
146+
iftc.assertFn!=nil {
147+
tc.assertFn(stdout.String(),userClient)
148+
}
149+
})
150+
}
151+
}
152+
153+
funcfakeAgentAPIEcho(ctx context.Context,t testing.TB,initMsg agentapisdk.Message,want...string)map[string]http.HandlerFunc {
154+
t.Helper()
155+
varmmu sync.RWMutex
156+
msgs:= []agentapisdk.Message{initMsg}
157+
wantCpy:=make([]string,len(want))
158+
copy(wantCpy,want)
159+
t.Cleanup(func() {
160+
mmu.Lock()
161+
defermmu.Unlock()
162+
if!t.Failed() {
163+
assert.Empty(t,wantCpy,"not all expected messages received: missing %v",wantCpy)
164+
}
165+
})
166+
writeAgentAPIError:=func(w http.ResponseWriter,errerror,statusint) {
167+
w.WriteHeader(status)
168+
_=json.NewEncoder(w).Encode(agentapisdk.ErrorModel{
169+
Errors:ptr.Ref([]agentapisdk.ErrorDetail{
170+
{
171+
Message:ptr.Ref(err.Error()),
172+
},
173+
}),
174+
})
175+
}
176+
returnmap[string]http.HandlerFunc{
177+
"/status":func(w http.ResponseWriter,r*http.Request) {
178+
w.Header().Set("Content-Type","application/json")
179+
_=json.NewEncoder(w).Encode(agentapisdk.GetStatusResponse{
180+
Status:"stable",
181+
})
182+
},
183+
"/messages":func(w http.ResponseWriter,r*http.Request) {
184+
w.Header().Set("Content-Type","application/json")
185+
mmu.RLock()
186+
defermmu.RUnlock()
187+
bs,err:=json.Marshal(agentapisdk.GetMessagesResponse{
188+
Messages:msgs,
189+
})
190+
iferr!=nil {
191+
writeAgentAPIError(w,err,http.StatusBadRequest)
192+
return
193+
}
194+
_,_=w.Write(bs)
195+
},
196+
"/message":func(w http.ResponseWriter,r*http.Request) {
197+
mmu.Lock()
198+
defermmu.Unlock()
199+
varparams agentapisdk.PostMessageParams
200+
w.Header().Set("Content-Type","application/json")
201+
err:=json.NewDecoder(r.Body).Decode(&params)
202+
if!assert.NoError(t,err,"decode message") {
203+
writeAgentAPIError(w,err,http.StatusBadRequest)
204+
return
205+
}
206+
207+
iflen(wantCpy)==0 {
208+
assert.Fail(t,"unexpected message","received message %v, but no more expected messages",params)
209+
writeAgentAPIError(w,xerrors.New("no more expected messages"),http.StatusBadRequest)
210+
return
211+
}
212+
exp:=wantCpy[0]
213+
wantCpy=wantCpy[1:]
214+
215+
if!assert.Equal(t,exp,params.Content,"message content mismatch") {
216+
writeAgentAPIError(w,xerrors.New("unexpected message content: expected "+exp+", got "+params.Content),http.StatusBadRequest)
217+
return
218+
}
219+
220+
msgs=append(msgs, agentapisdk.Message{
221+
Id:int64(len(msgs)+1),
222+
Content:params.Content,
223+
Role:agentapisdk.RoleUser,
224+
Time:time.Now().UTC(),
225+
})
226+
msgs=append(msgs, agentapisdk.Message{
227+
Id:int64(len(msgs)+1),
228+
Content:params.Content,
229+
Role:agentapisdk.RoleAgent,
230+
Time:time.Now().UTC(),
231+
})
232+
assert.NoError(t,json.NewEncoder(w).Encode(agentapisdk.PostMessageResponse{
233+
Ok:true,
234+
}))
235+
},
236+
}
237+
}
238+
21239
// setupCLITaskTest creates a test workspace with an AI task template and agent,
22240
// with a fake agent API configured with the provided set of handlers.
23241
// Returns the user client and workspace.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp