Files
k8sgpt/pkg/cache/interplex_based_test.go
Anas Khan 238a97af1c fix: guard interplex cache Store and Load against nil grpc conn (#1682)
grpc.NewClient returns a nil *ClientConn when it fails (for example on an
invalid connection string). Store and Load registered defer conn.Close()
before checking the returned error, so a client-creation failure triggered a
nil pointer dereference in the deferred Close instead of returning the error.

Move defer conn.Close() to after the error check in both methods. Add a
regression test that drives Store and Load with an invalid connection string
and asserts they return an error without panicking.

The deferred conn.Close() now discards its error with a plain _ = conn.Close()
rather than logging it: grpc's ClientConn.Close() only returns a non-nil error
on a double-close, which can't happen from a single call site here, so a
logging branch would be dead code that patch coverage can never satisfy.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
2026-07-06 13:13:57 +01:00

121 lines
3.1 KiB
Go

package cache
import (
rpc "buf.build/gen/go/interplex-ai/schemas/grpc/go/protobuf/schema/v1/schemav1grpc"
schemav1 "buf.build/gen/go/interplex-ai/schemas/protocolbuffers/go/protobuf/schema/v1"
"context"
"errors"
"google.golang.org/grpc"
"net"
"testing"
)
func TestInterplexCache(t *testing.T) {
cache := &InterplexCache{
configuration: InterplexCacheConfiguration{
ConnectionString: "localhost:50051",
},
}
// Mock GRPC server setup
errChan := make(chan error, 1)
go func() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
errChan <- err
return
}
s := grpc.NewServer()
rpc.RegisterCacheServiceServer(s, &mockCacheService{})
if err := s.Serve(lis); err != nil {
errChan <- err
return
}
}()
// Check if server startup failed
select {
case err := <-errChan:
if err != nil {
t.Fatalf("failed to start mock server: %v", err)
}
default:
// Server started successfully
}
t.Run("TestStore", func(t *testing.T) {
err := cache.Store("key1", "value1")
if err != nil {
t.Errorf("Error storing value: %v", err)
}
})
t.Run("TestLoad", func(t *testing.T) {
value, err := cache.Load("key1")
if err != nil {
t.Errorf("Error loading value: %v", err)
}
if value != "value1" {
t.Errorf("Expected value1, got %v", value)
}
})
t.Run("TestExists", func(t *testing.T) {
exists := cache.Exists("key1")
if !exists {
t.Errorf("Expected key1 to exist")
}
})
}
// TestInterplexCacheClientError ensures Store and Load return the connection
// error instead of panicking when grpc.NewClient fails. On error grpc.NewClient
// returns a nil *ClientConn, so a deferred conn.Close() placed before the error
// check dereferences nil.
func TestInterplexCacheClientError(t *testing.T) {
// Store/Load overwrite the connection string with localhost:8084 when
// INTERPLEX_LOCAL_MODE is set, which would bypass grpc.NewClient. Pin it empty
// so the invalid connection string is exercised regardless of the environment.
t.Setenv("INTERPLEX_LOCAL_MODE", "")
// An invalid URL escape makes grpc.NewClient fail and return a nil conn.
cache := &InterplexCache{
configuration: InterplexCacheConfiguration{
ConnectionString: "dns://%zz",
},
}
t.Run("Store", func(t *testing.T) {
if err := cache.Store("key1", "value1"); err == nil {
t.Error("expected an error for an invalid connection string, got nil")
}
})
t.Run("Load", func(t *testing.T) {
if _, err := cache.Load("key1"); err == nil {
t.Error("expected an error for an invalid connection string, got nil")
}
})
}
type mockCacheService struct {
rpc.UnimplementedCacheServiceServer
data map[string]string
}
func (m *mockCacheService) Set(ctx context.Context, req *schemav1.SetRequest) (*schemav1.SetResponse, error) {
if m.data == nil {
m.data = make(map[string]string)
}
m.data[req.Key] = req.Value
return &schemav1.SetResponse{}, nil
}
func (m *mockCacheService) Get(ctx context.Context, req *schemav1.GetRequest) (*schemav1.GetResponse, error) {
value, exists := m.data[req.Key]
if !exists {
return nil, errors.New("key not found")
}
return &schemav1.GetResponse{Value: value}, nil
}