@@ -73,80 +73,105 @@ func TestContainersHandler(t *testing.T) {
73
73
t .Run ("list" , func (t * testing.T ) {
74
74
t .Parallel ()
75
75
76
- // Given: a containersHandler backed by a mock
77
- var (
78
- ctx = testutil .Context (t , testutil .WaitShort )
79
- clk = quartz .NewMock (t )
80
- ctrl = gomock .NewController (t )
81
- mockLister = NewMockContainerLister (ctrl )
82
- now = time .Now ().UTC ()
83
- ch = containersHandler {
84
- cacheDuration : time .Second ,
85
- cl : mockLister ,
86
- clock : clk ,
87
- }
88
- expected = []codersdk.WorkspaceAgentContainer {fakeContainer (t )}
89
- )
90
-
91
- clk .Set (now ).MustWait (ctx )
92
-
93
- // When: getContainers is called for the first time
94
- ch .mtime = time.Time {}
95
- mockLister .EXPECT ().List (gomock .Any ()).Return (expected , nil )
96
- actual , err := ch .getContainers (ctx )
97
-
98
- // Then: the underlying lister is called and the result is returned
99
- require .NoError (t , err , "expected no error on first call" )
100
- require .Equal (t , expected , actual , "expected containers to be equal on first call" )
101
- // Then: the result is cached
102
- require .Equal (t , now , ch .mtime , "expected container mtime to be set on first call" )
103
- require .NotEmpty (t , ch .containers , "expected cached data to not be empty on first call" )
104
-
105
- // When: getContainers is called again
106
- actual , err = ch .getContainers (ctx )
107
-
108
- // Then: the underlying lister is not called and the cached result is
109
- // returned
110
- require .NoError (t , err , "expected no error on second call" )
111
- require .Equal (t , expected , actual , "expected containers to be equal on second call" )
112
- // Then: the result is cached
113
- require .Equal (t , now , ch .mtime , "expected container mtime to not have changed on second call" )
114
- require .Equal (t , expected , ch .containers , "expected cached data to not have changed on second call" )
115
-
116
- // When: getContainers is called after the cache duration has expired
117
- expected = append (expected , fakeContainer (t ))
118
- later := now .Add (defaultGetContainersCacheDuration ).Add (time .Second )
119
- clk .Set (later ).MustWait (ctx )
120
- mockLister .EXPECT ().List (gomock .Any ()).Return (expected , nil )
121
- actual , err = ch .getContainers (ctx )
122
-
123
- // Then: the underlying lister is called and the result is returned
124
- require .NoError (t , err , "expected no error on third call" )
125
- require .Equal (t , expected , actual , "expected containers to be equal on third call" )
126
- // Then: the result is cached
127
- require .Equal (t , later , ch .mtime , "expected container mtime to later on third call" )
128
- require .Equal (t , expected , ch .containers , "expected cached data to not have changed on third call" )
129
-
130
- // When: getContainers is called again but the underlying lister returns an error
131
- actual , err = ch .getContainers (ctx )
132
- require .NoError (t , err )
133
-
134
- // Then: the cached data is not updated
135
- require .Equal (t , expected , actual , "expected containers to be equal on fourth call" )
136
- require .Equal (t , later , ch .mtime , "expected container mtime to not have changed on fourth call" )
137
- require .Equal (t , expected , ch .containers , "expected cached data to not have changed on fourth call" )
138
-
139
- // When: time advances past mtime
140
- laterlater := later .Add (defaultGetContainersCacheDuration ).Add (time .Second )
141
- clk .Set (laterlater ).MustWait (ctx )
142
- mockLister .EXPECT ().List (gomock .Any ()).Return (nil , assert .AnError )
143
- actual , err = ch .getContainers (ctx )
144
- // Then: the underlying error is returned
145
- require .ErrorContains (t , err , assert .AnError .Error (), "expected error on fifth call" )
146
- require .Nil (t , actual , "expected no data to be returned on fifth call" )
147
- // Then: the underlying cached data remains the same
148
- require .Equal (t , later , ch .mtime , "expected container mtime to not have changed on fifth call" )
149
- require .Equal (t , expected , ch .containers , "expected cached data to not have changed on fifth call" )
76
+ fakeCt := fakeContainer (t )
77
+ fakeCt2 := fakeContainer (t )
78
+
79
+ // Each test case is called multiple times to ensure idempotency
80
+ for _ , tc := range []struct {
81
+ name string
82
+ // data to be stored in the handler
83
+ cacheData []codersdk.WorkspaceAgentContainer
84
+ // duration of cache
85
+ cacheDur time.Duration
86
+ // relative age of the cached data
87
+ cacheAge time.Duration
88
+ // function to set up expectations for the mock
89
+ setupMock func (* MockContainerLister )
90
+ // expected result
91
+ expected []codersdk.WorkspaceAgentContainer
92
+ // expected error
93
+ expectedErr string
94
+ }{
95
+ {
96
+ name : "no cache" ,
97
+ setupMock : func (mcl * MockContainerLister ) {
98
+ mcl .EXPECT ().List (gomock .Any ()).Return ([]codersdk.WorkspaceAgentContainer {fakeCt }, nil ).AnyTimes ()
99
+ },
100
+ expected : []codersdk.WorkspaceAgentContainer {fakeCt },
101
+ },
102
+ {
103
+ name : "no data" ,
104
+ cacheData : nil ,
105
+ cacheAge : 2 * time .Second ,
106
+ cacheDur : time .Second ,
107
+ setupMock : func (mcl * MockContainerLister ) {
108
+ mcl .EXPECT ().List (gomock .Any ()).Return ([]codersdk.WorkspaceAgentContainer {fakeCt }, nil ).AnyTimes ()
109
+ },
110
+ expected : []codersdk.WorkspaceAgentContainer {fakeCt },
111
+ },
112
+ {
113
+ name : "cached data" ,
114
+ cacheAge : time .Second ,
115
+ cacheData : []codersdk.WorkspaceAgentContainer {fakeCt },
116
+ cacheDur : 2 * time .Second ,
117
+ expected : []codersdk.WorkspaceAgentContainer {fakeCt },
118
+ },
119
+ {
120
+ name : "lister error" ,
121
+ setupMock : func (mcl * MockContainerLister ) {
122
+ mcl .EXPECT ().List (gomock .Any ()).Return (nil , assert .AnError ).AnyTimes ()
123
+ },
124
+ expectedErr : assert .AnError .Error (),
125
+ },
126
+ {
127
+ name : "stale cache" ,
128
+ cacheAge : 2 * time .Second ,
129
+ cacheData : []codersdk.WorkspaceAgentContainer {fakeCt },
130
+ cacheDur : time .Second ,
131
+ setupMock : func (mcl * MockContainerLister ) {
132
+ mcl .EXPECT ().List (gomock .Any ()).Return ([]codersdk.WorkspaceAgentContainer {fakeCt2 }, nil ).AnyTimes ()
133
+ },
134
+ expected : []codersdk.WorkspaceAgentContainer {fakeCt2 },
135
+ },
136
+ } {
137
+ tc := tc
138
+ t .Run (tc .name , func (t * testing.T ) {
139
+ t .Parallel ()
140
+ var (
141
+ ctx = testutil .Context (t , testutil .WaitShort )
142
+ clk = quartz .NewMock (t )
143
+ ctrl = gomock .NewController (t )
144
+ mockLister = NewMockContainerLister (ctrl )
145
+ now = time .Now ().UTC ()
146
+ ch = containersHandler {
147
+ cacheDuration : tc .cacheDur ,
148
+ cl : mockLister ,
149
+ clock : clk ,
150
+ containers : tc .cacheData ,
151
+ }
152
+ )
153
+ if tc .cacheAge != 0 {
154
+ ch .mtime = now .Add (- tc .cacheAge )
155
+ }
156
+ if tc .setupMock != nil {
157
+ tc .setupMock (mockLister )
158
+ }
159
+
160
+ clk .Set (now ).MustWait (ctx )
161
+
162
+ // Repeat the test to ensure idempotency
163
+ for i := 0 ; i < 2 ; i ++ {
164
+ actual , err := ch .getContainers (ctx )
165
+ if tc .expectedErr != "" {
166
+ require .Empty (t , actual , "expected no data (attempt %d)" , i )
167
+ require .ErrorContains (t , err , tc .expectedErr , "expected error (attempt %d)" , i )
168
+ } else {
169
+ require .NoError (t , err , "expected no error (attempt %d)" , i )
170
+ require .Equal (t , tc .expected , actual , "expected containers to be equal (attempt %d)" , i )
171
+ }
172
+ }
173
+ })
174
+ }
150
175
})
151
176
}
152
177
0 commit comments