@@ -161,11 +161,16 @@ func TestAPI(t *testing.T) {
161
161
return codersdk.WorkspaceAgentListContainersResponse {Containers : cts }
162
162
}
163
163
164
+ type initialDataPayload struct {
165
+ val codersdk.WorkspaceAgentListContainersResponse
166
+ err error
167
+ }
168
+
164
169
// Each test case is called multiple times to ensure idempotency
165
170
for _ , tc := range []struct {
166
171
name string
167
172
// initialData to be stored in the handler
168
- initialData codersdk. WorkspaceAgentListContainersResponse
173
+ initialData initialDataPayload
169
174
// function to set up expectations for the mock
170
175
setupMock func (mcl * acmock.MockLister , preReq * gomock.Call )
171
176
// expected result
@@ -175,72 +180,99 @@ func TestAPI(t *testing.T) {
175
180
}{
176
181
{
177
182
name : "no initial data" ,
178
- initialData : makeResponse (),
183
+ initialData : initialDataPayload { makeResponse (), nil } ,
179
184
setupMock : func (mcl * acmock.MockLister , preReq * gomock.Call ) {
180
185
mcl .EXPECT ().List (gomock .Any ()).Return (makeResponse (fakeCt ), nil ).After (preReq ).AnyTimes ()
181
186
},
182
187
expected : makeResponse (fakeCt ),
183
188
},
184
189
{
185
190
name : "repeat initial data" ,
186
- initialData : makeResponse (fakeCt ),
191
+ initialData : initialDataPayload { makeResponse (fakeCt ), nil } ,
187
192
expected : makeResponse (fakeCt ),
188
193
},
189
194
{
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 },
191
210
setupMock : func (mcl * acmock.MockLister , preReq * gomock.Call ) {
192
211
mcl .EXPECT ().List (gomock .Any ()).Return (makeResponse (), assert .AnError ).After (preReq ).AnyTimes ()
193
212
},
194
213
expectedErr : assert .AnError .Error (),
195
214
},
196
215
{
197
216
name : "updated data" ,
198
- initialData : makeResponse (fakeCt ),
217
+ initialData : initialDataPayload { makeResponse (fakeCt ), nil } ,
199
218
setupMock : func (mcl * acmock.MockLister , preReq * gomock.Call ) {
200
219
mcl .EXPECT ().List (gomock .Any ()).Return (makeResponse (fakeCt2 ), nil ).After (preReq ).AnyTimes ()
201
220
},
202
221
expected : makeResponse (fakeCt2 ),
203
222
},
204
223
} {
205
- tc := tc
206
224
t .Run (tc .name , func (t * testing.T ) {
207
225
t .Parallel ()
208
226
var (
209
227
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 )
213
232
logger = slogtest .Make (t , & slogtest.Options {IgnoreErrors : true }).Leveled (slog .LevelDebug )
214
233
r = chi .NewRouter ()
215
234
)
216
235
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 )
218
237
if tc .setupMock != nil {
219
- tc .setupMock (mockLister , initialData .Times (1 ))
238
+ tc .setupMock (mLister , initialDataCall .Times (1 ))
220
239
} else {
221
- initialData .AnyTimes ()
240
+ initialDataCall .AnyTimes ()
222
241
}
223
242
224
243
api := agentcontainers .NewAPI (logger ,
225
- agentcontainers .WithClock (clk ),
226
- agentcontainers .WithLister (mockLister ),
244
+ agentcontainers .WithClock (mClock ),
245
+ agentcontainers .WithLister (mLister ),
227
246
)
228
247
defer api .Close ()
229
248
r .Mount ("/" , api .Routes ())
230
249
250
+ // Make sure the ticker function has been registered
251
+ // before advancing the clock.
252
+ tickerTrap .MustWait (ctx ).Release ()
253
+ tickerTrap .Close ()
254
+
231
255
// Initial request returns the initial data.
232
256
req := httptest .NewRequest (http .MethodGet , "/" , nil ).
233
257
WithContext (ctx )
234
258
rec := httptest .NewRecorder ()
235
259
r .ServeHTTP (rec , req )
236
260
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
+ }
241
273
242
- // Advance the clock to run updateLoop .
243
- _ , aw := clk .AdvanceNext ()
274
+ // Advance the clock to run updaterLoop .
275
+ _ , aw := mClock .AdvanceNext ()
244
276
aw .MustWait (ctx )
245
277
246
278
// Second request returns the updated data.
@@ -750,34 +782,40 @@ func TestAPI(t *testing.T) {
750
782
ConfigPath : "/home/coder/project/.devcontainer/devcontainer.json" ,
751
783
}
752
784
785
+ ctx := testutil .Context (t , testutil .WaitShort )
786
+
753
787
logger := slogtest .Make (t , nil ).Leveled (slog .LevelDebug )
754
- lister := & fakeLister {
788
+ fLister := & fakeLister {
755
789
containers : codersdk.WorkspaceAgentListContainersResponse {
756
790
Containers : []codersdk.WorkspaceAgentContainer {container },
757
791
},
758
792
}
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
+
761
798
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 ),
765
802
agentcontainers .WithDevcontainers (
766
803
[]codersdk.WorkspaceAgentDevcontainer {dc },
767
804
[]codersdk.WorkspaceAgentScript {{LogSourceID : uuid .New (), ID : dc .ID }},
768
805
),
769
806
)
770
807
defer api .Close ()
771
808
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 ()
775
813
776
814
// Make sure the start loop has been called.
777
- watcher .waitNext (ctx )
815
+ fWatcher .waitNext (ctx )
778
816
779
817
// Simulate a file modification event to make the devcontainer dirty.
780
- watcher .sendEventWaitNextCalled (ctx , fsnotify.Event {
818
+ fWatcher .sendEventWaitNextCalled (ctx , fsnotify.Event {
781
819
Name : "/home/coder/project/.devcontainer/devcontainer.json" ,
782
820
Op : fsnotify .Write ,
783
821
})
@@ -799,11 +837,11 @@ func TestAPI(t *testing.T) {
799
837
800
838
// Next, simulate a situation where the container is no longer
801
839
// running.
802
- lister .containers .Containers = []codersdk.WorkspaceAgentContainer {}
840
+ fLister .containers .Containers = []codersdk.WorkspaceAgentContainer {}
803
841
804
842
// Trigger a refresh which will use the second response from mock
805
843
// lister (no containers).
806
- _ , aw := clk .AdvanceNext ()
844
+ _ , aw := mClock .AdvanceNext ()
807
845
aw .MustWait (ctx )
808
846
809
847
// Afterwards the devcontainer should not be running and not dirty.
@@ -828,9 +866,6 @@ func TestAPI(t *testing.T) {
828
866
ctx := testutil .Context (t , testutil .WaitShort )
829
867
830
868
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 )
834
869
835
870
// Create a fake container with a config file.
836
871
configPath := "/workspace/project/.devcontainer/devcontainer.json"
@@ -845,6 +880,10 @@ func TestAPI(t *testing.T) {
845
880
},
846
881
}
847
882
883
+ mClock := quartz .NewMock (t )
884
+ mClock .Set (startTime )
885
+ tickerTrap := mClock .Trap ().TickerFunc ("updaterLoop" )
886
+ fWatcher := newFakeWatcher (t )
848
887
fLister := & fakeLister {
849
888
containers : codersdk.WorkspaceAgentListContainersResponse {
850
889
Containers : []codersdk.WorkspaceAgentContainer {container },
@@ -863,6 +902,11 @@ func TestAPI(t *testing.T) {
863
902
r := chi .NewRouter ()
864
903
r .Mount ("/" , api .Routes ())
865
904
905
+ // Make sure the ticker function has been registered
906
+ // before advancing any use of mClock.Advance.
907
+ tickerTrap .MustWait (ctx )
908
+ tickerTrap .Close ()
909
+
866
910
// Call the list endpoint first to ensure config files are
867
911
// detected and watched.
868
912
req := httptest .NewRequest (http .MethodGet , "/devcontainers" , nil ).
@@ -895,7 +939,7 @@ func TestAPI(t *testing.T) {
895
939
Op : fsnotify .Write ,
896
940
})
897
941
898
- // Advance the clock to run updateLoop .
942
+ // Advance the clock to run updaterLoop .
899
943
_ , aw := mClock .AdvanceNext ()
900
944
aw .MustWait (ctx )
901
945
@@ -920,7 +964,7 @@ func TestAPI(t *testing.T) {
920
964
container .CreatedAt = mClock .Now () // Update the creation time.
921
965
fLister .containers .Containers = []codersdk.WorkspaceAgentContainer {container }
922
966
923
- // Advance the clock to run updateLoop .
967
+ // Advance the clock to run updaterLoop .
924
968
_ , aw = mClock .AdvanceNext ()
925
969
aw .MustWait (ctx )
926
970
0 commit comments