Skip to content

Commit 04431a5

Browse files
committed
update tests, add test for error during initial data
1 parent e225434 commit 04431a5

File tree

1 file changed

+82
-38
lines changed

1 file changed

+82
-38
lines changed

agent/agentcontainers/api_test.go

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,16 @@ func TestAPI(t *testing.T) {
161161
return codersdk.WorkspaceAgentListContainersResponse{Containers: cts}
162162
}
163163

164+
type initialDataPayload struct {
165+
val codersdk.WorkspaceAgentListContainersResponse
166+
err error
167+
}
168+
164169
// Each test case is called multiple times to ensure idempotency
165170
for _, tc := range []struct {
166171
name string
167172
// initialData to be stored in the handler
168-
initialData codersdk.WorkspaceAgentListContainersResponse
173+
initialData initialDataPayload
169174
// function to set up expectations for the mock
170175
setupMock func(mcl *acmock.MockLister, preReq *gomock.Call)
171176
// expected result
@@ -175,72 +180,99 @@ func TestAPI(t *testing.T) {
175180
}{
176181
{
177182
name: "no initial data",
178-
initialData: makeResponse(),
183+
initialData: initialDataPayload{makeResponse(), nil},
179184
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
180185
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt), nil).After(preReq).AnyTimes()
181186
},
182187
expected: makeResponse(fakeCt),
183188
},
184189
{
185190
name: "repeat initial data",
186-
initialData: makeResponse(fakeCt),
191+
initialData: initialDataPayload{makeResponse(fakeCt), nil},
187192
expected: makeResponse(fakeCt),
188193
},
189194
{
190-
name: "lister error",
195+
name: "lister error always",
196+
initialData: initialDataPayload{makeResponse(), assert.AnError},
197+
expectedErr: assert.AnError.Error(),
198+
},
199+
{
200+
name: "lister error only during initial data",
201+
initialData: initialDataPayload{makeResponse(), assert.AnError},
202+
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
203+
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt), nil).After(preReq).AnyTimes()
204+
},
205+
expectedErr: assert.AnError.Error(),
206+
},
207+
{
208+
name: "lister error after initial data",
209+
initialData: initialDataPayload{makeResponse(fakeCt), nil},
191210
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
192211
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(), assert.AnError).After(preReq).AnyTimes()
193212
},
194213
expectedErr: assert.AnError.Error(),
195214
},
196215
{
197216
name: "updated data",
198-
initialData: makeResponse(fakeCt),
217+
initialData: initialDataPayload{makeResponse(fakeCt), nil},
199218
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
200219
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt2), nil).After(preReq).AnyTimes()
201220
},
202221
expected: makeResponse(fakeCt2),
203222
},
204223
} {
205-
tc := tc
206224
t.Run(tc.name, func(t *testing.T) {
207225
t.Parallel()
208226
var (
209227
ctx = testutil.Context(t, testutil.WaitShort)
210-
clk = quartz.NewMock(t)
211-
ctrl = gomock.NewController(t)
212-
mockLister = acmock.NewMockLister(ctrl)
228+
mClock = quartz.NewMock(t)
229+
tickerTrap = mClock.Trap().TickerFunc("updaterLoop")
230+
mCtrl = gomock.NewController(t)
231+
mLister = acmock.NewMockLister(mCtrl)
213232
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
214233
r = chi.NewRouter()
215234
)
216235

217-
initialData := mockLister.EXPECT().List(gomock.Any()).Return(tc.initialData, nil)
236+
initialDataCall := mLister.EXPECT().List(gomock.Any()).Return(tc.initialData.val, tc.initialData.err)
218237
if tc.setupMock != nil {
219-
tc.setupMock(mockLister, initialData.Times(1))
238+
tc.setupMock(mLister, initialDataCall.Times(1))
220239
} else {
221-
initialData.AnyTimes()
240+
initialDataCall.AnyTimes()
222241
}
223242

224243
api := agentcontainers.NewAPI(logger,
225-
agentcontainers.WithClock(clk),
226-
agentcontainers.WithLister(mockLister),
244+
agentcontainers.WithClock(mClock),
245+
agentcontainers.WithLister(mLister),
227246
)
228247
defer api.Close()
229248
r.Mount("/", api.Routes())
230249

250+
// Make sure the ticker function has been registered
251+
// before advancing the clock.
252+
tickerTrap.MustWait(ctx).Release()
253+
tickerTrap.Close()
254+
231255
// Initial request returns the initial data.
232256
req := httptest.NewRequest(http.MethodGet, "/", nil).
233257
WithContext(ctx)
234258
rec := httptest.NewRecorder()
235259
r.ServeHTTP(rec, req)
236260

237-
var got codersdk.WorkspaceAgentListContainersResponse
238-
err := json.NewDecoder(rec.Body).Decode(&got)
239-
require.NoError(t, err, "unmarshal response failed")
240-
require.Equal(t, tc.initialData, got, "want initial data")
261+
if tc.initialData.err != nil {
262+
got := &codersdk.Error{}
263+
err := json.NewDecoder(rec.Body).Decode(got)
264+
require.NoError(t, err, "unmarshal response failed")
265+
require.ErrorContains(t, got, tc.initialData.err.Error(), "want error")
266+
return
267+
} else {
268+
var got codersdk.WorkspaceAgentListContainersResponse
269+
err := json.NewDecoder(rec.Body).Decode(&got)
270+
require.NoError(t, err, "unmarshal response failed")
271+
require.Equal(t, tc.initialData.val, got, "want initial data")
272+
}
241273

242-
// Advance the clock to run updateLoop.
243-
_, aw := clk.AdvanceNext()
274+
// Advance the clock to run updaterLoop.
275+
_, aw := mClock.AdvanceNext()
244276
aw.MustWait(ctx)
245277

246278
// Second request returns the updated data.
@@ -750,34 +782,40 @@ func TestAPI(t *testing.T) {
750782
ConfigPath: "/home/coder/project/.devcontainer/devcontainer.json",
751783
}
752784

785+
ctx := testutil.Context(t, testutil.WaitShort)
786+
753787
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
754-
lister := &fakeLister{
788+
fLister := &fakeLister{
755789
containers: codersdk.WorkspaceAgentListContainersResponse{
756790
Containers: []codersdk.WorkspaceAgentContainer{container},
757791
},
758792
}
759-
watcher := newFakeWatcher(t)
760-
clk := quartz.NewMock(t)
793+
fWatcher := newFakeWatcher(t)
794+
mClock := quartz.NewMock(t)
795+
mClock.Set(time.Now()).MustWait(ctx)
796+
tickerTrap := mClock.Trap().TickerFunc("updaterLoop")
797+
761798
api := agentcontainers.NewAPI(logger,
762-
agentcontainers.WithClock(clk),
763-
agentcontainers.WithLister(lister),
764-
agentcontainers.WithWatcher(watcher),
799+
agentcontainers.WithClock(mClock),
800+
agentcontainers.WithLister(fLister),
801+
agentcontainers.WithWatcher(fWatcher),
765802
agentcontainers.WithDevcontainers(
766803
[]codersdk.WorkspaceAgentDevcontainer{dc},
767804
[]codersdk.WorkspaceAgentScript{{LogSourceID: uuid.New(), ID: dc.ID}},
768805
),
769806
)
770807
defer api.Close()
771808

772-
ctx := testutil.Context(t, testutil.WaitShort)
773-
774-
clk.Set(time.Now()).MustWait(ctx)
809+
// Make sure the ticker function has been registered
810+
// before advancing any use of mClock.Advance.
811+
tickerTrap.MustWait(ctx).Release()
812+
tickerTrap.Close()
775813

776814
// Make sure the start loop has been called.
777-
watcher.waitNext(ctx)
815+
fWatcher.waitNext(ctx)
778816

779817
// Simulate a file modification event to make the devcontainer dirty.
780-
watcher.sendEventWaitNextCalled(ctx, fsnotify.Event{
818+
fWatcher.sendEventWaitNextCalled(ctx, fsnotify.Event{
781819
Name: "/home/coder/project/.devcontainer/devcontainer.json",
782820
Op: fsnotify.Write,
783821
})
@@ -799,11 +837,11 @@ func TestAPI(t *testing.T) {
799837

800838
// Next, simulate a situation where the container is no longer
801839
// running.
802-
lister.containers.Containers = []codersdk.WorkspaceAgentContainer{}
840+
fLister.containers.Containers = []codersdk.WorkspaceAgentContainer{}
803841

804842
// Trigger a refresh which will use the second response from mock
805843
// lister (no containers).
806-
_, aw := clk.AdvanceNext()
844+
_, aw := mClock.AdvanceNext()
807845
aw.MustWait(ctx)
808846

809847
// Afterwards the devcontainer should not be running and not dirty.
@@ -828,9 +866,6 @@ func TestAPI(t *testing.T) {
828866
ctx := testutil.Context(t, testutil.WaitShort)
829867

830868
startTime := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)
831-
mClock := quartz.NewMock(t)
832-
mClock.Set(startTime)
833-
fWatcher := newFakeWatcher(t)
834869

835870
// Create a fake container with a config file.
836871
configPath := "/workspace/project/.devcontainer/devcontainer.json"
@@ -845,6 +880,10 @@ func TestAPI(t *testing.T) {
845880
},
846881
}
847882

883+
mClock := quartz.NewMock(t)
884+
mClock.Set(startTime)
885+
tickerTrap := mClock.Trap().TickerFunc("updaterLoop")
886+
fWatcher := newFakeWatcher(t)
848887
fLister := &fakeLister{
849888
containers: codersdk.WorkspaceAgentListContainersResponse{
850889
Containers: []codersdk.WorkspaceAgentContainer{container},
@@ -863,6 +902,11 @@ func TestAPI(t *testing.T) {
863902
r := chi.NewRouter()
864903
r.Mount("/", api.Routes())
865904

905+
// Make sure the ticker function has been registered
906+
// before advancing any use of mClock.Advance.
907+
tickerTrap.MustWait(ctx).Release()
908+
tickerTrap.Close()
909+
866910
// Call the list endpoint first to ensure config files are
867911
// detected and watched.
868912
req := httptest.NewRequest(http.MethodGet, "/devcontainers", nil).
@@ -895,7 +939,7 @@ func TestAPI(t *testing.T) {
895939
Op: fsnotify.Write,
896940
})
897941

898-
// Advance the clock to run updateLoop.
942+
// Advance the clock to run updaterLoop.
899943
_, aw := mClock.AdvanceNext()
900944
aw.MustWait(ctx)
901945

@@ -920,7 +964,7 @@ func TestAPI(t *testing.T) {
920964
container.CreatedAt = mClock.Now() // Update the creation time.
921965
fLister.containers.Containers = []codersdk.WorkspaceAgentContainer{container}
922966

923-
// Advance the clock to run updateLoop.
967+
// Advance the clock to run updaterLoop.
924968
_, aw = mClock.AdvanceNext()
925969
aw.MustWait(ctx)
926970

0 commit comments

Comments
 (0)