package cluster_test import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "github.com/gin-gonic/gin" "github.com/go-logr/logr/testr" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" kruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache/informertest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "edge-infra.dev/pkg/edge/info" "edge-infra.dev/pkg/sds/interlock/internal/config" "edge-infra.dev/pkg/sds/interlock/internal/errors" "edge-infra.dev/pkg/sds/interlock/topic/cluster" "edge-infra.dev/pkg/sds/interlock/websocket" "edge-infra.dev/pkg/sds/lib/k8s/retryclient" ) var testClusterName = "test-cluster" func init() { gin.SetMode(gin.TestMode) // required to prevent errors around getting InCluster configuration for k8s // client os.Setenv("KUBERNETES_SERVICE_HOST", "1") os.Setenv("KUBERNETES_SERVICE_PORT", "1") // required to prevent hostname required error os.Setenv("NODE_NAME", "test-node") } func SetupTestCtx(t *testing.T) context.Context { logOptions := testr.Options{ LogTimestamp: true, Verbosity: -1, } ctx := ctrl.LoggerInto(context.Background(), testr.NewWithOptions(t, logOptions)) return ctx } func TestGetState(t *testing.T) { r, err := initializeClusterRouter(SetupTestCtx(t)) require.NoError(t, err) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", cluster.Path, nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) actual := &cluster.State{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), actual)) expected := &cluster.State{ Name: testClusterName, } assert.Equal(t, expected, actual) } type NameEditor struct { Name string `json:"name"` } type Errors struct { Errors []*errors.Error `json:"errors"` } func TestPatchState(t *testing.T) { r, err := initializeClusterRouter(SetupTestCtx(t)) require.NoError(t, err) testCases := map[string]struct { input interface{} expectedStatus int response interface{} expectedResponse interface{} }{ "Success": { input: struct{}{}, expectedStatus: http.StatusAccepted, response: &cluster.State{}, expectedResponse: &cluster.State{ Name: testClusterName, }, }, "Faillure_ReadOnlyName": { input: NameEditor{ Name: "any-name", }, expectedStatus: http.StatusBadRequest, response: &Errors{}, expectedResponse: &Errors{ Errors: []*errors.Error{ { Detail: errors.NewReadOnlyFieldMessage("Name"), }, }, }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { out, err := json.Marshal(tc.input) require.NoError(t, err) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", cluster.Path, bytes.NewBuffer(out)) r.ServeHTTP(w, req) assert.Equal(t, tc.expectedStatus, w.Code) require.NoError(t, json.Unmarshal(w.Body.Bytes(), tc.response)) assert.Equal(t, tc.expectedResponse, tc.response) }) } } func initializeClusterRouter(ctx context.Context) (*gin.Engine, error) { ei := &info.EdgeInfo{ BannerName: "example", ProjectID: "example", Store: testClusterName, Fleet: "example", ClusterType: "example", Location: "example", ForemanProjectID: "example", BannerEdgeID: "example", ClusterEdgeID: "example", EdgeAPIEndpoint: "example", } cm := ei.ToConfigMap() cli := getFakeKubeClient(cm) cfg := &config.Config{ Fs: afero.NewMemMapFs(), KubeRetryClient: retryclient.New(cli, cli, retryclient.Config{}), Cache: &informertest.FakeInformers{}, } wm := websocket.NewManager() c, err := cluster.New(ctx, cfg, wm) if err != nil { return nil, err } r := gin.Default() c.RegisterEndpoints(r) return r, nil } func getFakeKubeClient(initObjs ...client.Object) client.Client { return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build() } func createScheme() *kruntime.Scheme { scheme := kruntime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) return scheme }