88 "net/http/httptest"
99 "strings"
1010 "sync"
11+ "sync/atomic"
1112 "testing"
1213 "time"
1314
@@ -2817,7 +2818,7 @@ func TestWriteChannelBackpressure(t *testing.T) {
28172818func TestPingChannelFullScenario (t * testing.T ) {
28182819 t .Parallel ()
28192820
2820- pongReceived := false
2821+ var pongReceived atomic. Bool
28212822 srv := httptest .NewServer (websocket .Handler (func (ws * websocket.Conn ) {
28222823 var sub map [string ]any
28232824 _ = websocket .JSON .Receive (ws , & sub )
@@ -2836,7 +2837,7 @@ func TestPingChannelFullScenario(t *testing.T) {
28362837 "type" : "pong" ,
28372838 "seq" : fmt .Sprintf ("%v" , msg ["seq" ]),
28382839 })
2839- pongReceived = true
2840+ pongReceived . Store ( true )
28402841 }
28412842 }
28422843 }))
@@ -2863,7 +2864,7 @@ func TestPingChannelFullScenario(t *testing.T) {
28632864 time .Sleep (300 * time .Millisecond )
28642865 client .Stop ()
28652866
2866- if ! pongReceived {
2867+ if ! pongReceived . Load () {
28672868 t .Error ("Expected at least one pong to be received" )
28682869 }
28692870}
@@ -3000,7 +3001,7 @@ func TestConcurrentWebSocketClose(t *testing.T) {
30003001func TestNetworkErrorDuringWrite (t * testing.T ) {
30013002 t .Parallel ()
30023003
3003- connectionBroken := false
3004+ var connectionBroken atomic. Bool
30043005 srv := httptest .NewServer (websocket .Handler (func (ws * websocket.Conn ) {
30053006 var sub map [string ]any
30063007 _ = websocket .JSON .Receive (ws , & sub )
@@ -3011,19 +3012,19 @@ func TestNetworkErrorDuringWrite(t *testing.T) {
30113012
30123013 time .Sleep (50 * time .Millisecond )
30133014 ws .Close ()
3014- connectionBroken = true
3015+ connectionBroken . Store ( true )
30153016 }))
30163017 defer srv .Close ()
30173018
3018- errorReceived := false
3019+ var errorReceived atomic. Bool
30193020 client , err := New (Config {
30203021 ServerURL : "ws" + strings .TrimPrefix (srv .URL , "http" ),
30213022 Token : "test-token" ,
30223023 Organization : "test-org" ,
30233024 PingInterval : 20 * time .Millisecond ,
30243025 NoReconnect : true ,
30253026 OnDisconnect : func (err error ) {
3026- errorReceived = true
3027+ errorReceived . Store ( true )
30273028 },
30283029 })
30293030 if err != nil {
@@ -3035,7 +3036,7 @@ func TestNetworkErrorDuringWrite(t *testing.T) {
30353036
30363037 _ = client .Start (ctx )
30373038
3038- if connectionBroken && ! errorReceived {
3039+ if connectionBroken . Load () && ! errorReceived . Load () {
30393040 t .Log ("Warning: Network error may not have been properly detected" )
30403041 }
30413042}
@@ -3084,3 +3085,108 @@ func TestIOTimeoutRecovery(t *testing.T) {
30843085 t .Log ("Client successfully received event and handled I/O timeouts" )
30853086 }
30863087}
3088+
3089+ // TestEmptyResultCacheTTL tests that empty cache results expire after the TTL.
3090+ func TestEmptyResultCacheTTL (t * testing.T ) {
3091+ client , err := New (Config {
3092+ ServerURL : "ws://localhost:8080" ,
3093+ Token : "test-token" ,
3094+ Organization : "test-org" ,
3095+ NoReconnect : true ,
3096+ })
3097+ if err != nil {
3098+ t .Fatalf ("Failed to create client: %v" , err )
3099+ }
3100+
3101+ // Override TTL to a very short duration for testing
3102+ client .emptyResultTTL = 50 * time .Millisecond
3103+
3104+ key := "owner/repo:commit_empty"
3105+
3106+ // Simulate caching an empty result (as production code does)
3107+ client .cacheMu .Lock ()
3108+ client .commitCacheKeys = append (client .commitCacheKeys , key )
3109+ client .commitPRCache [key ] = []int {} // Empty result
3110+ client .commitPRCacheExpiry [key ] = time .Now ().Add (client .emptyResultTTL )
3111+ client .cacheMu .Unlock ()
3112+
3113+ // Verify cache entry exists and is not expired
3114+ client .cacheMu .RLock ()
3115+ cached , cacheExists := client .commitPRCache [key ]
3116+ expiry , hasExpiry := client .commitPRCacheExpiry [key ]
3117+ client .cacheMu .RUnlock ()
3118+
3119+ if ! cacheExists {
3120+ t .Fatal ("Expected cache entry to exist" )
3121+ }
3122+ if len (cached ) != 0 {
3123+ t .Errorf ("Expected empty cached result, got %v" , cached )
3124+ }
3125+ if ! hasExpiry {
3126+ t .Fatal ("Expected expiry to be set for empty result" )
3127+ }
3128+
3129+ // Check that it's not expired yet
3130+ cacheExpired := cacheExists && len (cached ) == 0 && hasExpiry && time .Now ().After (expiry )
3131+ if cacheExpired {
3132+ t .Error ("Cache should not be expired immediately after creation" )
3133+ }
3134+
3135+ // Wait for TTL to expire
3136+ time .Sleep (60 * time .Millisecond )
3137+
3138+ // Check that it's now expired
3139+ client .cacheMu .RLock ()
3140+ cached , cacheExists = client .commitPRCache [key ]
3141+ expiry , hasExpiry = client .commitPRCacheExpiry [key ]
3142+ client .cacheMu .RUnlock ()
3143+
3144+ cacheExpired = cacheExists && len (cached ) == 0 && hasExpiry && time .Now ().After (expiry )
3145+ if ! cacheExpired {
3146+ t .Errorf ("Cache should be expired after TTL, expiry=%v, now=%v" , expiry , time .Now ())
3147+ }
3148+ }
3149+
3150+ // TestNonEmptyResultNoCacheTTL tests that non-empty cache results don't have a TTL.
3151+ func TestNonEmptyResultNoCacheTTL (t * testing.T ) {
3152+ client , err := New (Config {
3153+ ServerURL : "ws://localhost:8080" ,
3154+ Token : "test-token" ,
3155+ Organization : "test-org" ,
3156+ NoReconnect : true ,
3157+ })
3158+ if err != nil {
3159+ t .Fatalf ("Failed to create client: %v" , err )
3160+ }
3161+
3162+ key := "owner/repo:commit_with_pr"
3163+
3164+ // Simulate caching a non-empty result
3165+ client .cacheMu .Lock ()
3166+ client .commitCacheKeys = append (client .commitCacheKeys , key )
3167+ client .commitPRCache [key ] = []int {123 , 456 } // Non-empty result
3168+ // Production code doesn't set expiry for non-empty results
3169+ client .cacheMu .Unlock ()
3170+
3171+ // Verify no expiry is set for non-empty results
3172+ client .cacheMu .RLock ()
3173+ cached , cacheExists := client .commitPRCache [key ]
3174+ _ , hasExpiry := client .commitPRCacheExpiry [key ]
3175+ client .cacheMu .RUnlock ()
3176+
3177+ if ! cacheExists {
3178+ t .Fatal ("Expected cache entry to exist" )
3179+ }
3180+ if len (cached ) != 2 {
3181+ t .Errorf ("Expected 2 cached PRs, got %v" , cached )
3182+ }
3183+ if hasExpiry {
3184+ t .Error ("Non-empty results should not have expiry set" )
3185+ }
3186+
3187+ // The cache entry should never be considered expired
3188+ cacheExpired := cacheExists && len (cached ) == 0 && hasExpiry && time .Now ().After (time.Time {})
3189+ if cacheExpired {
3190+ t .Error ("Non-empty cache should never be expired" )
3191+ }
3192+ }
0 commit comments