@@ -7,15 +7,19 @@ import (
77 "testing"
88 "time"
99
10- "github.com/containers/kubernetes-mcp-server/internal/test"
1110 "github.com/stretchr/testify/suite"
1211 "k8s.io/client-go/discovery"
1312 "k8s.io/client-go/discovery/cached/memory"
13+
14+ "github.com/containers/kubernetes-mcp-server/internal/test"
1415)
1516
1617const (
18+ // eventuallyTick is the polling interval for Eventually assertions
19+ eventuallyTick = time .Millisecond
1720 // watcherStateTimeout is the maximum time to wait for the watcher to capture initial state
1821 watcherStateTimeout = 100 * time .Millisecond
22+ watcherPollTimeout = 250 * time .Millisecond
1923)
2024
2125type ClusterStateTestSuite struct {
@@ -33,13 +37,13 @@ func (s *ClusterStateTestSuite) TearDownTest() {
3337 }
3438}
3539
36- // waitForWatcherState waits for the watcher to capture initial state
37- func (s * ClusterStateTestSuite ) waitForWatcherState (watcher * ClusterState ) {
38- s .NoError ( test . WaitForCondition ( watcherStateTimeout , func () bool {
40+ // waitForWatcherInitialState waits for the watcher to capture initial state
41+ func (s * ClusterStateTestSuite ) waitForWatcherInitialState (watcher * ClusterState ) {
42+ s .Eventually ( func () bool {
3943 watcher .mu .Lock ()
4044 defer watcher .mu .Unlock ()
4145 return len (watcher .lastKnownState .apiGroups ) > 0
42- }) , "timeout waiting for watcher to capture initial state" )
46+ }, watcherStateTimeout , eventuallyTick , "timeout waiting for watcher to capture initial state" )
4347}
4448
4549func (s * ClusterStateTestSuite ) TestNewClusterState () {
@@ -175,8 +179,7 @@ func (s *ClusterStateTestSuite) TestWatch() {
175179 }()
176180 s .T ().Cleanup (watcher .Close )
177181
178- // Wait for the watcher to capture initial state
179- s .waitForWatcherState (watcher )
182+ s .waitForWatcherInitialState (watcher )
180183
181184 s .Run ("captures API groups" , func () {
182185 s .NotEmpty (watcher .lastKnownState .apiGroups , "should capture at least one API group (apps)" )
@@ -212,18 +215,17 @@ func (s *ClusterStateTestSuite) TestWatch() {
212215 }()
213216 s .T ().Cleanup (watcher .Close )
214217
215- // Wait for initial state capture
216- s .waitForWatcherState (watcher )
218+ s .waitForWatcherInitialState (watcher )
217219
218220 // Modify the handler to add new API groups
219221 handler .Groups = []string {
220222 `{"name":"custom.example.com","versions":[{"groupVersion":"custom.example.com/v1","version":"v1"}],"preferredVersion":{"groupVersion":"custom.example.com/v1","version":"v1"}}` ,
221223 }
222224
223225 // Wait for change detection - the watcher invalidates the cache on each poll
224- s .Require (). NoError ( test . WaitForCondition ( 500 * time . Millisecond , func () bool {
226+ s .Eventually ( func () bool {
225227 return callCount .Load () >= 1
226- }) , "timeout waiting for onChange callback" )
228+ }, watcherPollTimeout , eventuallyTick , "timeout waiting for onChange callback" )
227229
228230 s .GreaterOrEqual (callCount .Load (), int32 (1 ), "onChange should be called at least once" )
229231 })
@@ -247,7 +249,7 @@ func (s *ClusterStateTestSuite) TestWatch() {
247249 s .T ().Cleanup (watcher .Close )
248250
249251 // Wait for the watcher to capture initial state
250- s .waitForWatcherState (watcher )
252+ s .waitForWatcherInitialState (watcher )
251253
252254 s .Run ("detects OpenShift via API groups" , func () {
253255 s .True (watcher .lastKnownState .isOpenShift )
@@ -280,17 +282,17 @@ func (s *ClusterStateTestSuite) TestWatch() {
280282 s .T ().Cleanup (watcher .Close )
281283
282284 // Wait for the watcher to start and capture initial state
283- s .waitForWatcherState (watcher )
285+ s .waitForWatcherInitialState (watcher )
284286
285287 // Modify the handler to trigger a change
286288 handler .Groups = []string {
287289 `{"name":"error.trigger","versions":[{"groupVersion":"error.trigger/v1","version":"v1"}],"preferredVersion":{"groupVersion":"error.trigger/v1","version":"v1"}}` ,
288290 }
289291
290292 // Wait for onChange to be called (which returns an error)
291- s .Require (). NoError ( test . WaitForCondition ( 500 * time . Millisecond , func () bool {
293+ s .Eventually ( func () bool {
292294 return errorCallCount .Load () >= 1
293- }) , "timeout waiting for onChange callback" )
295+ }, watcherPollTimeout , eventuallyTick , "timeout waiting for onChange callback" )
294296
295297 s .GreaterOrEqual (errorCallCount .Load (), int32 (1 ), "onChange should be called even when it returns an error" )
296298 })
@@ -303,6 +305,7 @@ func (s *ClusterStateTestSuite) TestClose() {
303305
304306 watcher := NewClusterState (discoveryClient )
305307 watcher .pollInterval = 50 * time .Millisecond
308+ watcher .debounceWindow = 10 * time .Millisecond
306309
307310 var callCount atomic.Int32
308311 onChange := func () error {
@@ -315,18 +318,17 @@ func (s *ClusterStateTestSuite) TestClose() {
315318 }()
316319
317320 // Wait for the watcher to start
318- s .waitForWatcherState (watcher )
321+ s .waitForWatcherInitialState (watcher )
319322
320323 watcher .Close ()
321324
322325 s .Run ("stops polling" , func () {
323326 beforeCount := callCount .Load ()
324327 // Wait longer than poll interval to verify no more polling
325- // We expect this to timeout because no callbacks should be triggered after close
326- err := test . WaitForCondition ( 150 * time . Millisecond , func () bool {
328+ // We expect this to never happen because no callbacks should be triggered after close
329+ s . Never ( func () bool {
327330 return callCount .Load () > beforeCount
328- })
329- s .Error (err , "should timeout because no polling occurs after close" )
331+ }, watcherPollTimeout , eventuallyTick , "should not poll after close" )
330332 afterCount := callCount .Load ()
331333 s .Equal (beforeCount , afterCount , "should not poll after close" )
332334 })
@@ -367,19 +369,19 @@ func (s *ClusterStateTestSuite) TestClose() {
367369 }()
368370
369371 // Wait for the watcher to start
370- s .waitForWatcherState (watcher )
372+ s .waitForWatcherInitialState (watcher )
371373
372374 // Modify the handler to trigger a change and start the debounce timer
373375 handler .Groups = []string {
374376 `{"name":"trigger.change","versions":[{"groupVersion":"trigger.change/v1","version":"v1"}],"preferredVersion":{"groupVersion":"trigger.change/v1","version":"v1"}}` ,
375377 }
376378
377379 // Wait for the change to be detected (debounce timer starts)
378- s .Require (). NoError ( test . WaitForCondition ( 200 * time . Millisecond , func () bool {
380+ s .Eventually ( func () bool {
379381 watcher .mu .Lock ()
380382 defer watcher .mu .Unlock ()
381383 return watcher .debounceTimer != nil
382- }) , "timeout waiting for debounce timer to start" )
384+ }, watcherPollTimeout , eventuallyTick , "timeout waiting for debounce timer to start" )
383385
384386 // Close the watcher before debounce window expires
385387 watcher .Close ()
0 commit comments