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

Commit9ee3a91

Browse files
authored
chore(cli): add integration-style test for exp task logs and send (#20345)
Adds some coderd integration tests for `coder exp tasks (send|logs)`.The actual agentapi interaction is faked out. I figure we don't want toactually start a real agentapi instance here.Authored by Claude with some manual cleanup.
1 parente8f0e3e commit9ee3a91

File tree

3 files changed

+504
-317
lines changed

3 files changed

+504
-317
lines changed

‎cli/exp_task_logs_test.go‎

Lines changed: 156 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package cli_test
22

33
import (
4-
"context"
54
"encoding/json"
6-
"fmt"
75
"net/http"
8-
"net/http/httptest"
96
"strings"
107
"testing"
118
"time"
@@ -14,7 +11,10 @@ import (
1411
"github.com/stretchr/testify/assert"
1512
"github.com/stretchr/testify/require"
1613

14+
agentapisdk"github.com/coder/agentapi-sdk-go"
15+
1716
"github.com/coder/coder/v2/cli/clitest"
17+
"github.com/coder/coder/v2/coderd/coderdtest"
1818
"github.com/coder/coder/v2/coderd/httpapi"
1919
"github.com/coder/coder/v2/codersdk"
2020
"github.com/coder/coder/v2/testutil"
@@ -23,178 +23,165 @@ import (
2323
funcTest_TaskLogs(t*testing.T) {
2424
t.Parallel()
2525

26-
var (
27-
clock=time.Date(2025,8,26,12,34,56,0,time.UTC)
28-
29-
taskID=uuid.MustParse("11111111-1111-1111-1111-111111111111")
30-
taskName="task-workspace"
31-
32-
taskLogs= []codersdk.TaskLogEntry{
33-
{
34-
ID:0,
35-
Content:"What is 1 + 1?",
36-
Type:codersdk.TaskLogTypeInput,
37-
Time:clock,
38-
},
39-
{
40-
ID:1,
41-
Content:"2",
42-
Type:codersdk.TaskLogTypeOutput,
43-
Time:clock.Add(1*time.Second),
44-
},
45-
}
46-
)
47-
48-
tests:= []struct {
49-
args []string
50-
expectTablestring
51-
expectLogs []codersdk.TaskLogEntry
52-
expectErrorstring
53-
handlerfunc(t*testing.T,ctx context.Context) http.HandlerFunc
54-
}{
55-
{
56-
args: []string{taskName,"--output","json"},
57-
expectLogs:taskLogs,
58-
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
59-
returnfunc(w http.ResponseWriter,r*http.Request) {
60-
switchr.URL.Path {
61-
casefmt.Sprintf("/api/v2/users/me/workspace/%s",taskName):
62-
httpapi.Write(ctx,w,http.StatusOK, codersdk.Workspace{
63-
ID:taskID,
64-
})
65-
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",taskID.String()):
66-
httpapi.Write(ctx,w,http.StatusOK, codersdk.TaskLogsResponse{
67-
Logs:taskLogs,
68-
})
69-
default:
70-
t.Errorf("unexpected path: %s",r.URL.Path)
71-
}
72-
}
73-
},
74-
},
75-
{
76-
args: []string{taskID.String(),"--output","json"},
77-
expectLogs:taskLogs,
78-
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
79-
returnfunc(w http.ResponseWriter,r*http.Request) {
80-
switchr.URL.Path {
81-
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",taskID.String()):
82-
httpapi.Write(ctx,w,http.StatusOK, codersdk.TaskLogsResponse{
83-
Logs:taskLogs,
84-
})
85-
default:
86-
t.Errorf("unexpected path: %s",r.URL.Path)
87-
}
88-
}
89-
},
90-
},
91-
{
92-
args: []string{taskID.String()},
93-
expectTable:`
94-
TYPE CONTENT
95-
input What is 1 + 1?
96-
output 2`,
97-
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
98-
returnfunc(w http.ResponseWriter,r*http.Request) {
99-
switchr.URL.Path {
100-
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",taskID.String()):
101-
httpapi.Write(ctx,w,http.StatusOK, codersdk.TaskLogsResponse{
102-
Logs:taskLogs,
103-
})
104-
default:
105-
t.Errorf("unexpected path: %s",r.URL.Path)
106-
}
107-
}
108-
},
109-
},
26+
testMessages:= []agentapisdk.Message{
11027
{
111-
args: []string{"doesnotexist"},
112-
expectError:httpapi.ResourceNotFoundResponse.Message,
113-
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
114-
returnfunc(w http.ResponseWriter,r*http.Request) {
115-
switchr.URL.Path {
116-
case"/api/v2/users/me/workspace/doesnotexist":
117-
httpapi.ResourceNotFound(w)
118-
default:
119-
t.Errorf("unexpected path: %s",r.URL.Path)
120-
}
121-
}
122-
},
28+
Id:0,
29+
Role:agentapisdk.RoleUser,
30+
Content:"What is 1 + 1?",
31+
Time:time.Now().Add(-2*time.Minute),
12332
},
12433
{
125-
args: []string{uuid.Nil.String()},// uuid does not exist
126-
expectError:httpapi.ResourceNotFoundResponse.Message,
127-
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
128-
returnfunc(w http.ResponseWriter,r*http.Request) {
129-
switchr.URL.Path {
130-
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",uuid.Nil.String()):
131-
httpapi.ResourceNotFound(w)
132-
default:
133-
t.Errorf("unexpected path: %s",r.URL.Path)
134-
}
135-
}
136-
},
34+
Id:1,
35+
Role:agentapisdk.RoleAgent,
36+
Content:"2",
37+
Time:time.Now().Add(-1*time.Minute),
13738
},
138-
{
139-
args: []string{"err-fetching-logs"},
140-
expectError:assert.AnError.Error(),
141-
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
142-
returnfunc(w http.ResponseWriter,r*http.Request) {
143-
switchr.URL.Path {
144-
case"/api/v2/users/me/workspace/err-fetching-logs":
145-
httpapi.Write(ctx,w,http.StatusOK, codersdk.Workspace{
146-
ID:taskID,
147-
})
148-
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",taskID.String()):
149-
httpapi.InternalServerError(w,assert.AnError)
150-
default:
151-
t.Errorf("unexpected path: %s",r.URL.Path)
152-
}
153-
}
154-
},
39+
}
40+
41+
t.Run("ByWorkspaceName_JSON",func(t*testing.T) {
42+
t.Parallel()
43+
ctx:=testutil.Context(t,testutil.WaitLong)
44+
45+
client,workspace:=setupCLITaskTest(ctx,t,fakeAgentAPITaskLogsOK(testMessages))
46+
userClient:=client// user already has access to their own workspace
47+
48+
varstdout strings.Builder
49+
inv,root:=clitest.New(t,"exp","task","logs",workspace.Name,"--output","json")
50+
inv.Stdout=&stdout
51+
clitest.SetupConfig(t,userClient,root)
52+
53+
err:=inv.WithContext(ctx).Run()
54+
require.NoError(t,err)
55+
56+
varlogs []codersdk.TaskLogEntry
57+
err=json.NewDecoder(strings.NewReader(stdout.String())).Decode(&logs)
58+
require.NoError(t,err)
59+
60+
require.Len(t,logs,2)
61+
require.Equal(t,"What is 1 + 1?",logs[0].Content)
62+
require.Equal(t,codersdk.TaskLogTypeInput,logs[0].Type)
63+
require.Equal(t,"2",logs[1].Content)
64+
require.Equal(t,codersdk.TaskLogTypeOutput,logs[1].Type)
65+
})
66+
67+
t.Run("ByWorkspaceID_JSON",func(t*testing.T) {
68+
t.Parallel()
69+
ctx:=testutil.Context(t,testutil.WaitLong)
70+
71+
client,workspace:=setupCLITaskTest(ctx,t,fakeAgentAPITaskLogsOK(testMessages))
72+
userClient:=client
73+
74+
varstdout strings.Builder
75+
inv,root:=clitest.New(t,"exp","task","logs",workspace.ID.String(),"--output","json")
76+
inv.Stdout=&stdout
77+
clitest.SetupConfig(t,userClient,root)
78+
79+
err:=inv.WithContext(ctx).Run()
80+
require.NoError(t,err)
81+
82+
varlogs []codersdk.TaskLogEntry
83+
err=json.NewDecoder(strings.NewReader(stdout.String())).Decode(&logs)
84+
require.NoError(t,err)
85+
86+
require.Len(t,logs,2)
87+
require.Equal(t,"What is 1 + 1?",logs[0].Content)
88+
require.Equal(t,codersdk.TaskLogTypeInput,logs[0].Type)
89+
require.Equal(t,"2",logs[1].Content)
90+
require.Equal(t,codersdk.TaskLogTypeOutput,logs[1].Type)
91+
})
92+
93+
t.Run("ByWorkspaceID_Table",func(t*testing.T) {
94+
t.Parallel()
95+
ctx:=testutil.Context(t,testutil.WaitLong)
96+
97+
client,workspace:=setupCLITaskTest(ctx,t,fakeAgentAPITaskLogsOK(testMessages))
98+
userClient:=client
99+
100+
varstdout strings.Builder
101+
inv,root:=clitest.New(t,"exp","task","logs",workspace.ID.String())
102+
inv.Stdout=&stdout
103+
clitest.SetupConfig(t,userClient,root)
104+
105+
err:=inv.WithContext(ctx).Run()
106+
require.NoError(t,err)
107+
108+
output:=stdout.String()
109+
require.Contains(t,output,"What is 1 + 1?")
110+
require.Contains(t,output,"2")
111+
require.Contains(t,output,"input")
112+
require.Contains(t,output,"output")
113+
})
114+
115+
t.Run("WorkspaceNotFound_ByName",func(t*testing.T) {
116+
t.Parallel()
117+
ctx:=testutil.Context(t,testutil.WaitLong)
118+
119+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerDaemon:true})
120+
owner:=coderdtest.CreateFirstUser(t,client)
121+
userClient,_:=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID)
122+
123+
varstdout strings.Builder
124+
inv,root:=clitest.New(t,"exp","task","logs","doesnotexist")
125+
inv.Stdout=&stdout
126+
clitest.SetupConfig(t,userClient,root)
127+
128+
err:=inv.WithContext(ctx).Run()
129+
require.Error(t,err)
130+
require.ErrorContains(t,err,httpapi.ResourceNotFoundResponse.Message)
131+
})
132+
133+
t.Run("WorkspaceNotFound_ByID",func(t*testing.T) {
134+
t.Parallel()
135+
ctx:=testutil.Context(t,testutil.WaitLong)
136+
137+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerDaemon:true})
138+
owner:=coderdtest.CreateFirstUser(t,client)
139+
userClient,_:=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID)
140+
141+
varstdout strings.Builder
142+
inv,root:=clitest.New(t,"exp","task","logs",uuid.Nil.String())
143+
inv.Stdout=&stdout
144+
clitest.SetupConfig(t,userClient,root)
145+
146+
err:=inv.WithContext(ctx).Run()
147+
require.Error(t,err)
148+
require.ErrorContains(t,err,httpapi.ResourceNotFoundResponse.Message)
149+
})
150+
151+
t.Run("ErrorFetchingLogs",func(t*testing.T) {
152+
t.Parallel()
153+
ctx:=testutil.Context(t,testutil.WaitLong)
154+
155+
client,workspace:=setupCLITaskTest(ctx,t,fakeAgentAPITaskLogsErr(assert.AnError))
156+
userClient:=client
157+
158+
inv,root:=clitest.New(t,"exp","task","logs",workspace.ID.String())
159+
clitest.SetupConfig(t,userClient,root)
160+
161+
err:=inv.WithContext(ctx).Run()
162+
require.ErrorContains(t,err,assert.AnError.Error())
163+
})
164+
}
165+
166+
funcfakeAgentAPITaskLogsOK(messages []agentapisdk.Message)map[string]http.HandlerFunc {
167+
returnmap[string]http.HandlerFunc{
168+
"/messages":func(w http.ResponseWriter,r*http.Request) {
169+
w.Header().Set("Content-Type","application/json")
170+
_=json.NewEncoder(w).Encode(map[string]interface{}{
171+
"messages":messages,
172+
})
155173
},
156174
}
175+
}
157176

158-
for_,tt:=rangetests {
159-
t.Run(strings.Join(tt.args,","),func(t*testing.T) {
160-
t.Parallel()
161-
162-
var (
163-
ctx=testutil.Context(t,testutil.WaitShort)
164-
srv=httptest.NewServer(tt.handler(t,ctx))
165-
client=codersdk.New(testutil.MustURL(t,srv.URL))
166-
args= []string{"exp","task","logs"}
167-
stdout strings.Builder
168-
errerror
169-
)
170-
171-
t.Cleanup(srv.Close)
172-
173-
inv,root:=clitest.New(t,append(args,tt.args...)...)
174-
inv.Stdout=&stdout
175-
inv.Stderr=&stdout
176-
clitest.SetupConfig(t,client,root)
177-
178-
err=inv.WithContext(ctx).Run()
179-
iftt.expectError=="" {
180-
assert.NoError(t,err)
181-
}else {
182-
assert.ErrorContains(t,err,tt.expectError)
183-
}
184-
185-
iftt.expectTable!="" {
186-
ifdiff:=tableDiff(tt.expectTable,stdout.String());diff!="" {
187-
t.Errorf("unexpected output diff (-want +got):\n%s",diff)
188-
}
189-
}
190-
191-
iftt.expectLogs!=nil {
192-
varlogs []codersdk.TaskLogEntry
193-
err=json.NewDecoder(strings.NewReader(stdout.String())).Decode(&logs)
194-
require.NoError(t,err)
195-
196-
assert.Equal(t,tt.expectLogs,logs)
197-
}
198-
})
177+
funcfakeAgentAPITaskLogsErr(errerror)map[string]http.HandlerFunc {
178+
returnmap[string]http.HandlerFunc{
179+
"/messages":func(w http.ResponseWriter,r*http.Request) {
180+
w.WriteHeader(http.StatusInternalServerError)
181+
w.Header().Set("Content-Type","application/json")
182+
_=json.NewEncoder(w).Encode(map[string]interface{}{
183+
"error":err.Error(),
184+
})
185+
},
199186
}
200187
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp