Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions pkg/agent/kube/testing.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kube

import (
"os"
"strings"

"github.com/kubestellar/console/pkg/k8s"
Expand All @@ -22,3 +23,26 @@ func NewTestKubectlProxy(config *api.Config) *KubectlProxy {
func AppendFormattedWarningEvents(sb *strings.Builder, events []k8s.Event) {
appendFormattedWarningEvents(sb, events)
}

// SetLookPathForTest replaces the lookPath function and returns a cleanup func.
// Use in tests outside the kube package to control tool detection.
func SetLookPathForTest(fn func(string) (string, error)) func() {
old := lookPath
lookPath = fn
return func() { lookPath = old }
}

// SetStatFileForTest replaces the statFile function and returns a cleanup func.
func SetStatFileForTest(fn func(string) (os.FileInfo, error)) func() {
old := statFile
statFile = fn
return func() { statFile = old }
}

// SetStandardToolCandidatesForTest replaces standardToolCandidates and returns a cleanup func.
func SetStandardToolCandidatesForTest(fn func(string) []string) func() {
old := standardToolCandidates
standardToolCandidates = fn
return func() { standardToolCandidates = old }
}

3 changes: 0 additions & 3 deletions pkg/agent/provider_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ var execCommand = exec.Command
// execCommandContext allows mocking exec.CommandContext for testing
var execCommandContext = exec.CommandContext

// fakeExecCommandContext is a test helper for mocking exec.CommandContext
var fakeExecCommandContext func(context.Context, string, ...string) *exec.Cmd

// aiProviderHTTPClient is reused across AI provider API calls to enable
// connection pooling and reduce per-request allocation overhead.
var aiProviderHTTPClient = newRestrictedAIProviderHTTPClient(aiProviderHTTPTimeout)
Expand Down
5 changes: 1 addition & 4 deletions pkg/agent/server_http_resources_stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,7 @@ func newTestKubectlProxy(clusterNames ...string) *kube.KubectlProxy {
if len(clusterNames) > 0 {
cfg.CurrentContext = clusterNames[0]
}
return &kube.KubectlProxy{
kubeconfig: "/dev/null",
config: cfg,
}
return kube.NewTestKubectlProxy(cfg)
}

// mustTestK8sClient creates a MultiClusterClient for testing (no actual cluster).
Expand Down
7 changes: 3 additions & 4 deletions pkg/agent/server_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

"github.com/kubestellar/console/pkg/agent/updater"
"github.com/kubestellar/console/pkg/k8s"
"github.com/kubestellar/console/pkg/settings"
appsv1 "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -251,7 +252,7 @@ func TestHandleAutoUpdateConfig_SaveAllError_Returns500(t *testing.T) {
mgr.SetSettingsPath("/dev/null/no-such-dir/settings.json")
defer mgr.SetSettingsPath(original)

checker := &UpdateChecker{channel: "stable"}
checker := updater.NewUpdateChecker(updater.UpdateCheckerConfig{})
server := &Server{
allowedOrigins: []string{"*"},
agentToken: "",
Expand All @@ -268,9 +269,7 @@ func TestHandleAutoUpdateConfig_SaveAllError_Returns500(t *testing.T) {
if w.Code != http.StatusInternalServerError {
t.Errorf("expected 500, got %d", w.Code)
}
checker.mu.Lock()
ch := checker.channel
checker.mu.Unlock()
ch := checker.Status().Channel
if ch != "stable" {
t.Errorf("updateChecker.Configure must not be called on SaveAll failure, but channel changed to %q", ch)
}
Expand Down
54 changes: 23 additions & 31 deletions pkg/agent/server_ops_clusters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"os/exec"
"testing"
"time"

"github.com/kubestellar/console/pkg/agent/kube"
)

func TestServer_HandleCloudCLIStatus(t *testing.T) {
Expand Down Expand Up @@ -39,20 +41,16 @@ func TestServer_HandleCloudCLIStatus(t *testing.T) {
}

func TestServer_HandleLocalClusterTools(t *testing.T) {
// Mock lookPath to simulate tool detection without invoking real executables.
oldLookPath := lookPath
oldStandardToolCandidates := standardToolCandidates
defer func() {
lookPath = oldLookPath
standardToolCandidates = oldStandardToolCandidates
}()
standardToolCandidates = func(string) []string { return nil }
lookPath = func(file string) (string, error) {
// Mock kube package vars to simulate tool detection without real executables.
restoreLookPath := kube.SetLookPathForTest(func(file string) (string, error) {
if file == "kind" {
return "/usr/local/bin/kind", nil
}
return "", &execError{file}
}
})
defer restoreLookPath()
restoreCandidates := kube.SetStandardToolCandidatesForTest(func(string) []string { return nil })
defer restoreCandidates()

// Mock execCommand so DetectTools does not invoke real binaries (e.g.
// "kind version"). The stub command exits 0 with empty output, which is
Expand All @@ -65,7 +63,7 @@ func TestServer_HandleLocalClusterTools(t *testing.T) {

s := &Server{
allowedOrigins: []string{"*"},
localClusters: NewLocalClusterManager(nil),
localClusters: kube.NewLocalClusterManager(nil),
}

req := httptest.NewRequest("GET", "/local-cluster-tools", nil)
Expand All @@ -78,7 +76,7 @@ func TestServer_HandleLocalClusterTools(t *testing.T) {
}

var resp struct {
Tools []LocalClusterTool `json:"tools"`
Tools []kube.LocalClusterTool `json:"tools"`
}
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatalf("Failed to decode response: %v", err)
Expand Down Expand Up @@ -109,31 +107,25 @@ func (f fakeExecutableInfoOps) IsDir() bool { return false }
func (f fakeExecutableInfoOps) Sys() interface{} { return nil }

func TestServer_HandleLocalClusterTools_RequestedToolsFallback(t *testing.T) {
oldLookPath := lookPath
oldStatFile := statFile
oldStandardToolCandidates := standardToolCandidates
defer func() {
lookPath = oldLookPath
statFile = oldStatFile
standardToolCandidates = oldStandardToolCandidates
}()

lookPath = func(file string) (string, error) {
restoreLookPath := kube.SetLookPathForTest(func(file string) (string, error) {
return "", &execError{file}
}
standardToolCandidates = func(name string) []string {
return []string{"/usr/local/bin/" + name}
}
statFile = func(name string) (os.FileInfo, error) {
})
defer restoreLookPath()
restoreStat := kube.SetStatFileForTest(func(name string) (os.FileInfo, error) {
if name == "/usr/local/bin/helm" {
return fakeExecutableInfoOps{name: "helm"}, nil
}
return nil, errors.New("not found")
}
})
defer restoreStat()
restoreCandidates := kube.SetStandardToolCandidatesForTest(func(name string) []string {
return []string{"/usr/local/bin/" + name}
})
defer restoreCandidates()

s := &Server{
allowedOrigins: []string{"*"},
localClusters: NewLocalClusterManager(nil),
localClusters: kube.NewLocalClusterManager(nil),
}

req := httptest.NewRequest("GET", "/local-cluster-tools?tool=helm", nil)
Expand All @@ -149,7 +141,7 @@ func TestServer_HandleLocalClusterTools_RequestedToolsFallback(t *testing.T) {
}

var resp struct {
Tools []LocalClusterTool `json:"tools"`
Tools []kube.LocalClusterTool `json:"tools"`
}
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatalf("Failed to decode response: %v", err)
Expand All @@ -172,7 +164,7 @@ func TestServer_HandleLocalClusters_List(t *testing.T) {

s := &Server{
allowedOrigins: []string{"*"},
localClusters: NewLocalClusterManager(nil),
localClusters: kube.NewLocalClusterManager(nil),
}

req := httptest.NewRequest("GET", "/local-clusters", nil)
Expand Down
3 changes: 2 additions & 1 deletion pkg/agent/server_ops_insights_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package agent

import (
"github.com/kubestellar/console/pkg/agent/kube"
"bytes"
"encoding/json"
"net/http"
Expand Down Expand Up @@ -85,7 +86,7 @@ func TestServer_HandleVClusterCheck(t *testing.T) {

s := &Server{
allowedOrigins: []string{"*"},
localClusters: &LocalClusterManager{},
localClusters: &kube.LocalClusterManager{},
}

req := httptest.NewRequest("GET", "/vcluster/check", nil)
Expand Down
7 changes: 2 additions & 5 deletions pkg/agent/server_sse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ func newTestServerForSSE(t *testing.T, contexts map[string]*api.Context) (*Serve
cfg.AuthInfos[name] = &api.AuthInfo{}
}

proxy := &kube.KubectlProxy{
kubeconfig: "/dev/null",
config: cfg,
}
proxy := kube.NewTestKubectlProxy(cfg)

srv := &Server{
k8sClient: k8sMock,
Expand Down Expand Up @@ -562,7 +559,7 @@ func TestHandleJobsStreamSSE_Unauthorized(t *testing.T) {

func TestHandleJobsStreamSSE_NilK8sClient(t *testing.T) {
srv := &Server{
kubectl: &kube.KubectlProxy{kubeconfig: "/dev/null", config: &api.Config{}},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
k8sClient: nil,
allowedOrigins: []string{"*"},
agentToken: "",
Expand Down
44 changes: 16 additions & 28 deletions pkg/agent/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestServer_HandleHealth_CORS(t *testing.T) {
server := &Server{
allowedOrigins: []string{"http://allowed.com"},
registry: &Registry{providers: make(map[string]AIProvider)},
kubectl: &kube.KubectlProxy{config: &api.Config{}},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
}

// Case 1: Allowed Origin
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestServer_HandleStatus(t *testing.T) {
},
}
server := &Server{
kubectl: &kube.KubectlProxy{config: config},
kubectl: kube.NewTestKubectlProxy(config),
allowedOrigins: []string{"http://allowed.com"},
agentToken: "test-token",
tokenExplicit: true,
Expand Down Expand Up @@ -225,7 +225,7 @@ func TestServer_HandleClustersHTTP(t *testing.T) {
"c1": {Server: "https://c1.com"},
},
}
mockProxy := &kube.KubectlProxy{config: config}
mockProxy := kube.NewTestKubectlProxy(config)
server := &Server{
kubectl: mockProxy,
allowedOrigins: []string{"*"},
Expand Down Expand Up @@ -271,10 +271,7 @@ func TestServer_HandleRenameContextHTTP(t *testing.T) {
execCommandContext = fakeExecCommandContext

// Setup proxy
proxy := &kube.KubectlProxy{
kubeconfig: "/tmp/config",
config: &api.Config{},
}
proxy := kube.NewTestKubectlProxy(&api.Config{})

server := &Server{
kubectl: proxy,
Expand Down Expand Up @@ -323,10 +320,7 @@ func TestServer_ResourceHandlers(t *testing.T) {
config := &api.Config{
CurrentContext: "ctx-1",
}
proxy := &kube.KubectlProxy{
config: config,
kubeconfig: "/tmp/config",
}
proxy := kube.NewTestKubectlProxy(config)

// Create mock k8s client
k8sClient, _ := k8s.NewMultiClusterClient("")
Expand Down Expand Up @@ -973,7 +967,7 @@ func TestMatchOrigin(t *testing.T) {

func TestServer_HandleClustersHTTP_Unauthorized(t *testing.T) {
server := &Server{
kubectl: &kube.KubectlProxy{config: &api.Config{}},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
agentToken: "secret", // require token
allowedOrigins: []string{"*"},
}
Expand All @@ -992,7 +986,7 @@ func TestServer_HandleClustersHTTP_Unauthorized(t *testing.T) {

func TestServer_HandleClustersHTTP_OPTIONS(t *testing.T) {
server := &Server{
kubectl: &kube.KubectlProxy{config: &api.Config{}},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
allowedOrigins: []string{"http://allowed.com"},
}

Expand Down Expand Up @@ -1434,7 +1428,7 @@ func (fakeFileInfo) Sys() interface{} { return nil }

func TestServer_HandleRenameContextHTTP_Unauthorized(t *testing.T) {
server := &Server{
kubectl: &kube.KubectlProxy{config: &api.Config{}},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
agentToken: "secret",
allowedOrigins: []string{"*"},
}
Expand All @@ -1452,7 +1446,7 @@ func TestServer_HandleRenameContextHTTP_Unauthorized(t *testing.T) {

func TestServer_HandleRenameContextHTTP_WrongMethod(t *testing.T) {
server := &Server{
kubectl: &kube.KubectlProxy{config: &api.Config{}},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
allowedOrigins: []string{"*"},
}

Expand All @@ -1472,10 +1466,7 @@ func TestServer_HandleRenameContextHTTP_MissingNames(t *testing.T) {
execCommandContext = fakeExecCommandContext

server := &Server{
kubectl: &kube.KubectlProxy{
config: &api.Config{},
kubeconfig: "/tmp/config",
},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
allowedOrigins: []string{"*"},
}

Expand All @@ -1499,10 +1490,7 @@ func TestServer_HandleRenameContextHTTP_FlagInjection(t *testing.T) {
execCommandContext = fakeExecCommandContext

server := &Server{
kubectl: &kube.KubectlProxy{
config: &api.Config{},
kubeconfig: "/tmp/config",
},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
allowedOrigins: []string{"*"},
}

Expand Down Expand Up @@ -2130,7 +2118,7 @@ func TestServer_ValidateAPIKeyValue_EmptyKey(t *testing.T) {

func TestServer_HandleHealth_OPTIONS(t *testing.T) {
server := &Server{
kubectl: &kube.KubectlProxy{config: &api.Config{}},
kubectl: kube.NewTestKubectlProxy(&api.Config{}),
registry: &Registry{providers: make(map[string]AIProvider)},
allowedOrigins: []string{"http://localhost"},
}
Expand Down Expand Up @@ -3417,7 +3405,7 @@ func TestCheckPingHealth_AllScenarios(t *testing.T) {

func TestServer_HandleLocalClusterTools_GET(t *testing.T) {
server := &Server{
localClusters: NewLocalClusterManager(nil),
localClusters: kube.NewLocalClusterManager(nil),
allowedOrigins: []string{"*"},
}

Expand All @@ -3433,7 +3421,7 @@ func TestServer_HandleLocalClusterTools_GET(t *testing.T) {

func TestServer_HandleLocalClusters_GET(t *testing.T) {
server := &Server{
localClusters: NewLocalClusterManager(nil),
localClusters: kube.NewLocalClusterManager(nil),
allowedOrigins: []string{"*"},
}

Expand All @@ -3449,7 +3437,7 @@ func TestServer_HandleLocalClusters_GET(t *testing.T) {

func TestServer_HandleLocalClusters_WrongMethod(t *testing.T) {
server := &Server{
localClusters: NewLocalClusterManager(nil),
localClusters: kube.NewLocalClusterManager(nil),
allowedOrigins: []string{"*"},
}

Expand All @@ -3464,7 +3452,7 @@ func TestServer_HandleLocalClusters_WrongMethod(t *testing.T) {

func TestServer_HandleLocalClusterTools_WrongMethod(t *testing.T) {
server := &Server{
localClusters: NewLocalClusterManager(nil),
localClusters: kube.NewLocalClusterManager(nil),
allowedOrigins: []string{"*"},
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package handlers
package transport

import (
"sync"
Expand Down
Loading
Loading