From 709a2dbb6ec387255c2a1cc7518d1edad07dad29 Mon Sep 17 00:00:00 2001 From: Eder Ignatowicz Date: Fri, 1 Nov 2024 09:24:13 -0400 Subject: [PATCH] feat(bff): Use envtest for kubernetes testing instead of hardcoded mock (#490) * feat(bff): Use envtest for kubernetes testing instead of hardcoded mock Signed-off-by: Eder Ignatowicz * feat(bff): using ginkgo and gomega as our test suite Signed-off-by: Eder Ignatowicz * PR review from Griffin Signed-off-by: Eder Ignatowicz --------- Signed-off-by: Eder Ignatowicz --- clients/ui/bff/Makefile | 20 +- clients/ui/bff/go.mod | 8 + clients/ui/bff/internal/api/app.go | 3 +- .../api/model_registry_handler_test.go | 82 +++---- .../api/model_versions_handler_test.go | 158 +++++++------- .../api/registered_models_handler_test.go | 181 ++++++++-------- clients/ui/bff/internal/api/suite_test.go | 56 +++++ clients/ui/bff/internal/api/test_utils.go | 9 +- clients/ui/bff/internal/integrations/k8s.go | 28 ++- clients/ui/bff/internal/mocks/k8s_mock.go | 203 ++++++++++++++++-- .../ui/bff/internal/mocks/k8s_mock_test.go | 62 ++++++ clients/ui/bff/internal/mocks/suite_test.go | 52 +++++ .../repositories/model_registry_test.go | 36 ++-- .../bff/internal/repositories/suite_test.go | 59 +++++ 14 files changed, 700 insertions(+), 257 deletions(-) create mode 100644 clients/ui/bff/internal/api/suite_test.go create mode 100644 clients/ui/bff/internal/mocks/k8s_mock_test.go create mode 100644 clients/ui/bff/internal/mocks/suite_test.go create mode 100644 clients/ui/bff/internal/repositories/suite_test.go diff --git a/clients/ui/bff/Makefile b/clients/ui/bff/Makefile index f58c08d1..dec0984d 100644 --- a/clients/ui/bff/Makefile +++ b/clients/ui/bff/Makefile @@ -3,6 +3,8 @@ IMG ?= model-registry-bff:latest PORT ?= 4000 MOCK_K8S_CLIENT ?= false MOCK_MR_CLIENT ?= false +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.29.0 .PHONY: all all: build @@ -32,7 +34,8 @@ vet: . go vet ./... .PHONY: test -test: +test: fmt vet envtest + ENVTEST_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" \ go test ./... .PHONY: build @@ -40,7 +43,8 @@ build: fmt vet test go build -o bin/bff cmd/main.go .PHONY: run -run: fmt vet +run: fmt vet envtest + ENVTEST_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" \ go run ./cmd/main.go --port=$(PORT) --mock-k8s-client=$(MOCK_K8S_CLIENT) --mock-mr-client=$(MOCK_MR_CLIENT) .PHONY: docker-build @@ -54,8 +58,18 @@ LOCALBIN ?= $(shell pwd)/bin $(LOCALBIN): mkdir -p $(LOCALBIN) +## Tool Binaries +ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) + +## Tool Versions GOLANGCI_LINT_VERSION ?= v1.57.2 +ENVTEST_VERSION ?= release-0.17 + +.PHONY: envtest +envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. +$(ENVTEST): $(LOCALBIN) + $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) .PHONY: golangci-lint golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. @@ -75,4 +89,4 @@ echo "Downloading $${package}" ;\ GOBIN=$(LOCALBIN) go install $${package} ;\ mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ } -endef \ No newline at end of file +endef diff --git a/clients/ui/bff/go.mod b/clients/ui/bff/go.mod index 02e58ce6..d6065a53 100644 --- a/clients/ui/bff/go.mod +++ b/clients/ui/bff/go.mod @@ -6,6 +6,8 @@ require ( github.com/brianvoe/gofakeit/v7 v7.0.4 github.com/julienschmidt/httprouter v1.3.0 github.com/kubeflow/model-registry v0.2.9 + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 github.com/stretchr/testify v1.9.0 k8s.io/api v0.31.2 k8s.io/apimachinery v0.31.2 @@ -22,15 +24,18 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -48,6 +53,8 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect @@ -55,6 +62,7 @@ require ( golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/clients/ui/bff/internal/api/app.go b/clients/ui/bff/internal/api/app.go index 6cf2ce6a..93b63566 100644 --- a/clients/ui/bff/internal/api/app.go +++ b/clients/ui/bff/internal/api/app.go @@ -45,7 +45,8 @@ func NewApp(cfg config.EnvConfig, logger *slog.Logger) (*App, error) { var err error if cfg.MockK8Client { //mock all k8s calls - k8sClient, err = mocks.NewKubernetesClient(logger) + ctx, cancel := context.WithCancel(context.Background()) + k8sClient, err = mocks.NewKubernetesClient(logger, ctx, cancel) } else { k8sClient, err = integrations.NewKubernetesClient(logger) } diff --git a/clients/ui/bff/internal/api/model_registry_handler_test.go b/clients/ui/bff/internal/api/model_registry_handler_test.go index e345fe9a..13c460fe 100644 --- a/clients/ui/bff/internal/api/model_registry_handler_test.go +++ b/clients/ui/bff/internal/api/model_registry_handler_test.go @@ -2,50 +2,52 @@ package api import ( "encoding/json" - "github.com/kubeflow/model-registry/ui/bff/internal/mocks" "github.com/kubeflow/model-registry/ui/bff/internal/models" "github.com/kubeflow/model-registry/ui/bff/internal/repositories" - "github.com/stretchr/testify/assert" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "io" "net/http" "net/http/httptest" - "testing" ) -func TestModelRegistryHandler(t *testing.T) { - mockK8sClient, _ := mocks.NewKubernetesClient(nil) - mockMRClient, _ := mocks.NewModelRegistryClient(nil) - - testApp := App{ - kubernetesClient: mockK8sClient, - repositories: repositories.NewRepositories(mockMRClient), - } - - req, err := http.NewRequest(http.MethodGet, ModelRegistryListPath, nil) - assert.NoError(t, err) - - rr := httptest.NewRecorder() - - testApp.ModelRegistryHandler(rr, req, nil) - rs := rr.Result() - - defer rs.Body.Close() - body, err := io.ReadAll(rs.Body) - assert.NoError(t, err) - var actual ModelRegistryListEnvelope - err = json.Unmarshal(body, &actual) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, rr.Code) - - var expected = ModelRegistryListEnvelope{ - Data: []models.ModelRegistryModel{ - {Name: "model-registry", Description: "Model registry description", DisplayName: "Model Registry"}, - {Name: "model-registry-dora", Description: "Model registry dora description", DisplayName: "Model Registry Dora"}, - {Name: "model-registry-bella", Description: "Model registry bella description", DisplayName: "Model Registry Bella"}, - }, - } - - assert.Equal(t, expected, actual) - -} +var _ = Describe("TestModelRegistryHandler", func() { + Context("fetching model registries", Ordered, func() { + It("should retrieve the model registries successfully", func() { + + By("creating the test app") + testApp := App{ + kubernetesClient: k8sClient, + repositories: repositories.NewRepositories(mockMRClient), + logger: logger, + } + + By("creating the http test infrastructure") + req, err := http.NewRequest(http.MethodGet, ModelRegistryListPath, nil) + Expect(err).NotTo(HaveOccurred()) + rr := httptest.NewRecorder() + + By("creating the http request for the handler") + testApp.ModelRegistryHandler(rr, req, nil) + rs := rr.Result() + defer rs.Body.Close() + body, err := io.ReadAll(rs.Body) + Expect(err).NotTo(HaveOccurred()) + + By("unmarshalling the model registries") + var actual ModelRegistryListEnvelope + err = json.Unmarshal(body, &actual) + Expect(err).NotTo(HaveOccurred()) + Expect(rr.Code).To(Equal(http.StatusOK)) + + By("should match the expected model registries") + var expected = []models.ModelRegistryModel{ + {Name: "model-registry", Description: "Model Registry Description", DisplayName: "Model Registry"}, + {Name: "model-registry-bella", Description: "Model Registry Bella description", DisplayName: "Model Registry Bella"}, + {Name: "model-registry-dora", Description: "Model Registry Dora description", DisplayName: "Model Registry Dora"}, + } + Expect(actual.Data).To(ConsistOf(expected)) + }) + + }) +}) diff --git a/clients/ui/bff/internal/api/model_versions_handler_test.go b/clients/ui/bff/internal/api/model_versions_handler_test.go index 877856c4..729aa715 100644 --- a/clients/ui/bff/internal/api/model_versions_handler_test.go +++ b/clients/ui/bff/internal/api/model_versions_handler_test.go @@ -3,80 +3,90 @@ package api import ( "github.com/kubeflow/model-registry/pkg/openapi" "github.com/kubeflow/model-registry/ui/bff/internal/mocks" - "github.com/stretchr/testify/assert" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "net/http" - "testing" ) -func TestGetModelVersionHandler(t *testing.T) { - data := mocks.GetModelVersionMocks()[0] - expected := ModelVersionEnvelope{Data: &data} - - actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/model_versions/1", nil) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, rs.StatusCode) - assert.Equal(t, expected.Data.Name, actual.Data.Name) -} - -func TestCreateModelVersionHandler(t *testing.T) { - data := mocks.GetModelVersionMocks()[0] - expected := ModelVersionEnvelope{Data: &data} - - body := ModelVersionEnvelope{Data: openapi.NewModelVersion("Model One", "1")} - - actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/model_versions", body) - assert.NoError(t, err) - - assert.Equal(t, http.StatusCreated, rs.StatusCode) - assert.Equal(t, expected.Data.Name, actual.Data.Name) - assert.Equal(t, rs.Header.Get("Location"), "/api/v1/model_registry/model-registry/model_versions/1") -} - -func TestUpdateModelVersionHandler(t *testing.T) { - data := mocks.GetModelVersionMocks()[0] - expected := ModelVersionEnvelope{Data: &data} - - reqData := openapi.ModelVersionUpdate{ - Description: openapi.PtrString("New description"), - } - body := ModelVersionUpdateEnvelope{Data: &reqData} - - actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodPatch, "/api/v1/model_registry/model-registry/model_versions/1", body) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, rs.StatusCode) - assert.Equal(t, expected.Data.Name, actual.Data.Name) -} - -func TestGetAllModelArtifactsByModelVersionHandler(t *testing.T) { - data := mocks.GetModelArtifactListMock() - expected := ModelArtifactListEnvelope{Data: &data} - - actual, rs, err := setupApiTest[ModelArtifactListEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/model_versions/1/artifacts", nil) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, rs.StatusCode) - assert.Equal(t, expected.Data.Size, actual.Data.Size) - assert.Equal(t, expected.Data.PageSize, actual.Data.PageSize) - assert.Equal(t, expected.Data.NextPageToken, actual.Data.NextPageToken) - assert.Equal(t, len(expected.Data.Items), len(actual.Data.Items)) -} - -func TestCreateModelArtifactByModelVersionHandler(t *testing.T) { - data := mocks.GetModelArtifactMocks()[0] - expected := ModelArtifactEnvelope{Data: &data} - - artifact := openapi.ModelArtifact{ - Name: openapi.PtrString("Artifact One"), - ArtifactType: "ARTIFACT_TYPE_ONE", - } - body := ModelArtifactEnvelope{Data: &artifact} - - actual, rs, err := setupApiTest[ModelArtifactEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/model_versions/1/artifacts", body) - assert.NoError(t, err) - - assert.Equal(t, http.StatusCreated, rs.StatusCode) - assert.Equal(t, expected.Data.GetArtifactType(), actual.Data.GetArtifactType()) - assert.Equal(t, rs.Header.Get("Location"), "/api/v1/model_registry/model-registry/model_artifacts/1") -} +var _ = Describe("TestGetModelVersionHandler", func() { + Context("testing Model Version Handler", Ordered, func() { + + It("should retrieve a model version", func() { + By("fetching a model version") + data := mocks.GetModelVersionMocks()[0] + expected := ModelVersionEnvelope{Data: &data} + actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/model_versions/1", nil, k8sClient) + Expect(err).NotTo(HaveOccurred()) + By("should match the expected model version") + Expect(rs.StatusCode).To(Equal(http.StatusOK)) + Expect(actual.Data.Name).To(Equal(expected.Data.Name)) + }) + + It("should create a model version", func() { + By("creating a model version") + data := mocks.GetModelVersionMocks()[0] + expected := ModelVersionEnvelope{Data: &data} + body := ModelVersionEnvelope{Data: openapi.NewModelVersion("Model One", "1")} + actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/model_versions", body, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should match the expected model version created") + Expect(rs.StatusCode).To(Equal(http.StatusCreated)) + Expect(actual.Data.Name).To(Equal(expected.Data.Name)) + Expect(rs.Header.Get("Location")).To(Equal("/api/v1/model_registry/model-registry/model_versions/1")) + }) + + It("should updated a model version", func() { + By("updating a model version") + data := mocks.GetModelVersionMocks()[0] + expected := ModelVersionEnvelope{Data: &data} + + reqData := openapi.ModelVersionUpdate{ + Description: openapi.PtrString("New description"), + } + body := ModelVersionUpdateEnvelope{Data: &reqData} + + actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodPatch, "/api/v1/model_registry/model-registry/model_versions/1", body, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should match the expected model version updated") + Expect(rs.StatusCode).To(Equal(http.StatusOK)) + Expect(actual.Data.Name).To(Equal(expected.Data.Name)) + }) + + It("get all model artifacts by a model version", func() { + By("getting a model artifacts by model version") + data := mocks.GetModelArtifactListMock() + expected := ModelArtifactListEnvelope{Data: &data} + actual, rs, err := setupApiTest[ModelArtifactListEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/model_versions/1/artifacts", nil, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should get all expected model version artifacts") + Expect(rs.StatusCode).To(Equal(http.StatusOK)) + Expect(actual.Data.Size).To(Equal(expected.Data.Size)) + Expect(actual.Data.PageSize).To(Equal(expected.Data.PageSize)) + Expect(actual.Data.NextPageToken).To(Equal(expected.Data.NextPageToken)) + Expect(len(actual.Data.Items)).To(Equal(len(expected.Data.Items))) + }) + + It("create Model Artifact By Model Version", func() { + By("creating a model version") + data := mocks.GetModelArtifactMocks()[0] + expected := ModelArtifactEnvelope{Data: &data} + + artifact := openapi.ModelArtifact{ + Name: openapi.PtrString("Artifact One"), + ArtifactType: "ARTIFACT_TYPE_ONE", + } + body := ModelArtifactEnvelope{Data: &artifact} + actual, rs, err := setupApiTest[ModelArtifactEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/model_versions/1/artifacts", body, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should get all expected model artifacts") + Expect(rs.StatusCode).To(Equal(http.StatusCreated)) + Expect(actual.Data.GetArtifactType()).To(Equal(expected.Data.GetArtifactType())) + Expect(rs.Header.Get("Location")).To(Equal("/api/v1/model_registry/model-registry/model_artifacts/1")) + + }) + }) +}) diff --git a/clients/ui/bff/internal/api/registered_models_handler_test.go b/clients/ui/bff/internal/api/registered_models_handler_test.go index 33b073d4..34bdbc1a 100644 --- a/clients/ui/bff/internal/api/registered_models_handler_test.go +++ b/clients/ui/bff/internal/api/registered_models_handler_test.go @@ -3,92 +3,101 @@ package api import ( "github.com/kubeflow/model-registry/pkg/openapi" "github.com/kubeflow/model-registry/ui/bff/internal/mocks" - "github.com/stretchr/testify/assert" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "net/http" - "testing" ) -func TestGetRegisteredModelHandler(t *testing.T) { - data := mocks.GetRegisteredModelMocks()[0] - expected := RegisteredModelEnvelope{Data: &data} - - actual, rs, err := setupApiTest[RegisteredModelEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/registered_models/1", nil) - assert.NoError(t, err) - - //TODO assert the full structure, I couldn't get unmarshalling to work for the full customProperties values - // this issue is in the test only - assert.Equal(t, http.StatusOK, rs.StatusCode) - assert.Equal(t, expected.Data.Name, actual.Data.Name) -} - -func TestGetAllRegisteredModelsHandler(t *testing.T) { - data := mocks.GetRegisteredModelListMock() - expected := RegisteredModelListEnvelope{Data: &data} - - actual, rs, err := setupApiTest[RegisteredModelListEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/registered_models", nil) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, rs.StatusCode) - assert.Equal(t, expected.Data.Size, actual.Data.Size) - assert.Equal(t, expected.Data.PageSize, actual.Data.PageSize) - assert.Equal(t, expected.Data.NextPageToken, actual.Data.NextPageToken) - assert.Equal(t, len(expected.Data.Items), len(actual.Data.Items)) -} - -func TestCreateRegisteredModelHandler(t *testing.T) { - data := mocks.GetRegisteredModelMocks()[0] - expected := RegisteredModelEnvelope{Data: &data} - - body := RegisteredModelEnvelope{Data: openapi.NewRegisteredModel("Model One")} - - actual, rs, err := setupApiTest[RegisteredModelEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/registered_models", body) - assert.NoError(t, err) - - assert.Equal(t, http.StatusCreated, rs.StatusCode) - assert.Equal(t, expected.Data.Name, actual.Data.Name) - assert.Equal(t, rs.Header.Get("location"), "/api/v1/model_registry/model-registry/registered_models/1") -} - -func TestUpdateRegisteredModelHandler(t *testing.T) { - data := mocks.GetRegisteredModelMocks()[0] - expected := RegisteredModelEnvelope{Data: &data} - - reqData := openapi.RegisteredModelUpdate{ - Description: openapi.PtrString("This is a new description"), - } - body := RegisteredModelUpdateEnvelope{Data: &reqData} - - actual, rs, err := setupApiTest[RegisteredModelEnvelope](http.MethodPatch, "/api/v1/model_registry/model-registry/registered_models/1", body) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, rs.StatusCode) - //TODO when mock client can handle changing state, update this to verify the changes are made. - assert.Equal(t, expected.Data.Description, actual.Data.Description) -} - -func TestGetAllModelVersionsForRegisteredModelHandler(t *testing.T) { - data := mocks.GetModelVersionListMock() - expected := ModelVersionListEnvelope{Data: &data} - - actual, rs, err := setupApiTest[ModelVersionListEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/registered_models/1/versions", nil) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, rs.StatusCode) - assert.Equal(t, expected.Data.Size, actual.Data.Size) - assert.Equal(t, expected.Data.PageSize, actual.Data.PageSize) - assert.Equal(t, expected.Data.NextPageToken, actual.Data.NextPageToken) - assert.Equal(t, len(expected.Data.Items), len(actual.Data.Items)) -} - -func TestCreateModelVersionForRegisteredModelHandler(t *testing.T) { - data := mocks.GetModelVersionMocks()[0] - expected := ModelVersionEnvelope{Data: &data} - - body := ModelVersionEnvelope{Data: openapi.NewModelVersion("Version Fifty", "")} - actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/registered_models/1/versions", body) - assert.NoError(t, err) - - assert.Equal(t, http.StatusCreated, rs.StatusCode) - assert.Equal(t, expected.Data.Name, actual.Data.Name) - assert.Equal(t, rs.Header.Get("Location"), "/api/v1/model_registry/model-registry/model_versions/1") -} +var _ = Describe("TestGetRegisteredModelHandler", func() { + Context("testing registered models by id", Ordered, func() { + + It("should retrieve a registered model", func() { + By("fetching all model registries") + data := mocks.GetRegisteredModelMocks()[0] + expected := RegisteredModelEnvelope{Data: &data} + actual, rs, err := setupApiTest[RegisteredModelEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/registered_models/1", nil, k8sClient) + Expect(err).NotTo(HaveOccurred()) + By("should match the expected model registry") + //TODO assert the full structure, I couldn't get unmarshalling to work for the full customProperties values + // this issue is in the test only + Expect(rs.StatusCode).To(Equal(http.StatusOK)) + Expect(actual.Data.Name).To(Equal(expected.Data.Name)) + }) + + It("should retrieve all registered models", func() { + By("fetching all registered models") + data := mocks.GetRegisteredModelListMock() + expected := RegisteredModelListEnvelope{Data: &data} + actual, rs, err := setupApiTest[RegisteredModelListEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/registered_models", nil, k8sClient) + Expect(err).NotTo(HaveOccurred()) + By("should match the expected model registry") + Expect(rs.StatusCode).To(Equal(http.StatusOK)) + Expect(actual.Data.Size).To(Equal(expected.Data.Size)) + Expect(actual.Data.PageSize).To(Equal(expected.Data.PageSize)) + Expect(actual.Data.NextPageToken).To(Equal(expected.Data.NextPageToken)) + Expect(len(actual.Data.Items)).To(Equal(len(expected.Data.Items))) + }) + + It("creating registered models", func() { + By("post to registered models") + data := mocks.GetRegisteredModelMocks()[0] + expected := RegisteredModelEnvelope{Data: &data} + body := RegisteredModelEnvelope{Data: openapi.NewRegisteredModel("Model One")} + actual, rs, err := setupApiTest[RegisteredModelEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/registered_models", body, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should do a successful post") + Expect(rs.StatusCode).To(Equal(http.StatusCreated)) + Expect(actual.Data.Name).To(Equal(expected.Data.Name)) + Expect(rs.Header.Get("location")).To(Equal("/api/v1/model_registry/model-registry/registered_models/1")) + }) + + It("updating registered models", func() { + By("path to registered models") + data := mocks.GetRegisteredModelMocks()[0] + expected := RegisteredModelEnvelope{Data: &data} + reqData := openapi.RegisteredModelUpdate{ + Description: openapi.PtrString("This is a new description"), + } + body := RegisteredModelUpdateEnvelope{Data: &reqData} + actual, rs, err := setupApiTest[RegisteredModelEnvelope](http.MethodPatch, "/api/v1/model_registry/model-registry/registered_models/1", body, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should do a successful patch") + Expect(rs.StatusCode).To(Equal(http.StatusOK)) + Expect(actual.Data.Description).To(Equal(expected.Data.Description)) + }) + + It("get all model versions for registered model", func() { + By("get to registered models versions") + data := mocks.GetModelVersionListMock() + expected := ModelVersionListEnvelope{Data: &data} + + actual, rs, err := setupApiTest[ModelVersionListEnvelope](http.MethodGet, "/api/v1/model_registry/model-registry/registered_models/1/versions", nil, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should get all items") + Expect(rs.StatusCode).To(Equal(http.StatusOK)) + Expect(actual.Data.Size).To(Equal(expected.Data.Size)) + Expect(actual.Data.PageSize).To(Equal(expected.Data.PageSize)) + Expect(actual.Data.NextPageToken).To(Equal(expected.Data.NextPageToken)) + Expect(len(actual.Data.Items)).To(Equal(len(expected.Data.Items))) + }) + + It("create model version for registered model", func() { + By("doing a post to registered model versions") + data := mocks.GetModelVersionMocks()[0] + expected := ModelVersionEnvelope{Data: &data} + + body := ModelVersionEnvelope{Data: openapi.NewModelVersion("Version Fifty", "")} + actual, rs, err := setupApiTest[ModelVersionEnvelope](http.MethodPost, "/api/v1/model_registry/model-registry/registered_models/1/versions", body, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + By("should successfully create it") + Expect(rs.StatusCode).To(Equal(http.StatusCreated)) + Expect(actual.Data.Name).To(Equal(expected.Data.Name)) + Expect(rs.Header.Get("Location")).To(Equal("/api/v1/model_registry/model-registry/model_versions/1")) + + }) + }) +}) diff --git a/clients/ui/bff/internal/api/suite_test.go b/clients/ui/bff/internal/api/suite_test.go new file mode 100644 index 00000000..a8197593 --- /dev/null +++ b/clients/ui/bff/internal/api/suite_test.go @@ -0,0 +1,56 @@ +package api + +import ( + "context" + k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations" + "github.com/kubeflow/model-registry/ui/bff/internal/mocks" + "log/slog" + "os" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "testing" +) + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + k8sClient k8s.KubernetesClientInterface + mockMRClient *mocks.ModelRegistryClientMock + ctx context.Context + cancel context.CancelFunc + logger *slog.Logger + err error +) + +func TestAPI(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "API Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + ctx, cancel = context.WithCancel(context.Background()) + + By("bootstrapping test environment") + logger = slog.New(slog.NewTextHandler(os.Stdout, nil)) + + k8sClient, err = mocks.NewKubernetesClient(logger, ctx, cancel) + Expect(err).NotTo(HaveOccurred()) + + mockMRClient, err = mocks.NewModelRegistryClient(nil) + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + defer cancel() + err := k8sClient.Shutdown(ctx, logger) + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/clients/ui/bff/internal/api/test_utils.go b/clients/ui/bff/internal/api/test_utils.go index ca144eaa..94bc598f 100644 --- a/clients/ui/bff/internal/api/test_utils.go +++ b/clients/ui/bff/internal/api/test_utils.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations" "github.com/kubeflow/model-registry/ui/bff/internal/mocks" "github.com/kubeflow/model-registry/ui/bff/internal/repositories" "io" @@ -11,21 +12,17 @@ import ( "net/http/httptest" ) -func setupApiTest[T any](method string, url string, body interface{}) (T, *http.Response, error) { +func setupApiTest[T any](method string, url string, body interface{}, k8sClient k8s.KubernetesClientInterface) (T, *http.Response, error) { mockMRClient, err := mocks.NewModelRegistryClient(nil) if err != nil { return *new(T), nil, err } - mockK8sClient, err := mocks.NewKubernetesClient(nil) - if err != nil { - return *new(T), nil, err - } mockClient := new(mocks.MockHTTPClient) testApp := App{ repositories: repositories.NewRepositories(mockMRClient), - kubernetesClient: mockK8sClient, + kubernetesClient: k8sClient, } var req *http.Request diff --git a/clients/ui/bff/internal/integrations/k8s.go b/clients/ui/bff/internal/integrations/k8s.go index 9402fcf1..60729a10 100644 --- a/clients/ui/bff/internal/integrations/k8s.go +++ b/clients/ui/bff/internal/integrations/k8s.go @@ -14,7 +14,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" ) -const componentName = "model-registry-server" +const ComponentName = "model-registry-server" type KubernetesClientInterface interface { GetServiceNames() ([]string, error) @@ -37,7 +37,7 @@ type KubernetesClient struct { Mgr ctrl.Manager Token string Logger *slog.Logger - stopFn context.CancelFunc // Store a function to cancel the context for graceful shutdown + StopFn context.CancelFunc // Store a function to cancel the context for graceful shutdown mgrStopped chan struct{} } @@ -98,7 +98,7 @@ func NewKubernetesClient(logger *slog.Logger) (KubernetesClientInterface, error) Mgr: mgr, Token: kubeconfig.BearerToken, Logger: logger, - stopFn: cancel, + StopFn: cancel, mgrStopped: mgrStopped, // Store the stop channel //Namespace: namespace, //TODO (ederign) do we need to restrict service list by namespace? @@ -110,7 +110,7 @@ func (kc *KubernetesClient) Shutdown(ctx context.Context, logger *slog.Logger) e logger.Info("shutting down Kubernetes manager...") // Use the saved cancel function to stop the manager - kc.stopFn() + kc.StopFn() // Wait for the manager to stop or for the context to be canceled select { @@ -151,13 +151,13 @@ func (kc *KubernetesClient) GetServiceNames() ([]string, error) { var serviceNames []string for _, service := range serviceList.Items { - if value, ok := service.Spec.Selector["component"]; ok && value == componentName { + if value, ok := service.Spec.Selector["component"]; ok && value == ComponentName { serviceNames = append(serviceNames, service.Name) } } if len(serviceNames) == 0 { - return nil, fmt.Errorf("no services found with component: %s", componentName) + return nil, fmt.Errorf("no services found with component: %s", ComponentName) } return serviceNames, nil @@ -183,7 +183,7 @@ func (kc *KubernetesClient) GetServiceDetails() ([]ServiceDetails, error) { var services []ServiceDetails for _, service := range serviceList.Items { - if svcComponent, exists := service.Spec.Selector["component"]; exists && svcComponent == componentName { + if svcComponent, exists := service.Spec.Selector["component"]; exists && svcComponent == ComponentName { var httpPort int32 hasHTTPPort := false for _, port := range service.Spec.Ports { @@ -203,14 +203,20 @@ func (kc *KubernetesClient) GetServiceDetails() ([]ServiceDetails, error) { continue } - displayName := service.Annotations["displayName"] + displayName := "" + description := "" + + if service.Annotations != nil { + displayName = service.Annotations["displayName"] + description = service.Annotations["description"] + } + if displayName == "" { - kc.Logger.Error("service missing displayName annotation", "serviceName", service.Name) + kc.Logger.Warn("service missing displayName annotation", "serviceName", service.Name) } - description := service.Annotations["description"] if description == "" { - kc.Logger.Error("service missing description annotation", "serviceName", service.Name) + kc.Logger.Warn("service missing description annotation", "serviceName", service.Name) } serviceDetails := ServiceDetails{ diff --git a/clients/ui/bff/internal/mocks/k8s_mock.go b/clients/ui/bff/internal/mocks/k8s_mock.go index b1ac66bc..bfdbfdaf 100644 --- a/clients/ui/bff/internal/mocks/k8s_mock.go +++ b/clients/ui/bff/internal/mocks/k8s_mock.go @@ -2,45 +2,210 @@ package mocks import ( "context" + "fmt" k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations" - "github.com/stretchr/testify/mock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" "log/slog" + "os" + "path/filepath" + "runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" ) type KubernetesClientMock struct { - mock.Mock + *k8s.KubernetesClient + testEnv *envtest.Environment } func (m *KubernetesClientMock) Shutdown(ctx context.Context, logger *slog.Logger) error { - logger.Error("Shutdown was called in mock") + logger.Info("Shutdown was called in mock") + m.StopFn() + err := m.testEnv.Stop() + if err != nil { + logger.Error("timeout while waiting for Kubernetes manager to stop") + return fmt.Errorf("timeout while waiting for Kubernetes manager to stop") + } + logger.Info("Shutdown ended successfully") return nil } -func NewKubernetesClient(_ *slog.Logger) (k8s.KubernetesClientInterface, error) { - return &KubernetesClientMock{}, nil +func NewKubernetesClient(logger *slog.Logger, ctx context.Context, cancel context.CancelFunc) (k8s.KubernetesClientInterface, error) { + + projectRoot, err := getProjectRoot() + if err != nil { + logger.Error("failed to find project root to locate binaries", slog.String("error", err.Error())) + cancel() + os.Exit(1) + } + + testEnv := &envtest.Environment{ + // The BinaryAssetsDirectory is only required if you want to run the tests directly without call the makefile target test. + // If not informed it will look for the default path defined in bff which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform the tests directly. + // When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join(projectRoot, "bin", "k8s", fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + cfg, err := testEnv.Start() + if err != nil { + logger.Error("failed to start test environment", err) + cancel() + os.Exit(1) + } + + mockK8sClient, err := client.New(cfg, client.Options{Scheme: scheme.Scheme}) + if err != nil { + logger.Error("failed to create Kubernetes client", err) + cancel() + os.Exit(1) + } + + err = setupMock(mockK8sClient, ctx) + if err != nil { + logger.Error("failed on mock setup", err) + cancel() + os.Exit(1) + } + + return &KubernetesClientMock{ + KubernetesClient: &k8s.KubernetesClient{ + Client: mockK8sClient, + Logger: logger, + StopFn: cancel, + }, + testEnv: testEnv, + }, nil +} + +func getProjectRoot() (string, error) { + currentDir, err := os.Getwd() + if err != nil { + return "", err + } + + for { + if _, err := os.Stat(filepath.Join(currentDir, "go.mod")); err == nil { + // Found the project root where go.mod is located + return currentDir, nil + } + + parentDir := filepath.Dir(currentDir) + if parentDir == currentDir { + // We reached the root directory and did not find the go.mod + return "", fmt.Errorf("could not find project root") + } + + currentDir = parentDir + } } -func (m *KubernetesClientMock) GetServiceNames() ([]string, error) { - return []string{"model-registry", "model-registry-dora", "model-registry-bella"}, nil +func setupMock(mockK8sClient client.Client, ctx context.Context) error { + err := createService(mockK8sClient, ctx, "model-registry", "default", "Model Registry", "Model Registry Description", "10.0.0.10") + if err != nil { + return err + } + err = createService(mockK8sClient, ctx, "model-registry-dora", "default", "Model Registry Dora", "Model Registry Dora description", "10.0.0.11") + if err != nil { + return err + } + err = createService(mockK8sClient, ctx, "model-registry-bella", "default", "Model Registry Bella", "Model Registry Bella description", "10.0.0.12") + if err != nil { + return err + } + return nil } func (m *KubernetesClientMock) GetServiceDetails() ([]k8s.ServiceDetails, error) { - return []k8s.ServiceDetails{ - {Name: "model-registry", Description: "Model registry description", DisplayName: "Model Registry"}, - {Name: "model-registry-dora", Description: "Model registry dora description", DisplayName: "Model Registry Dora"}, - {Name: "model-registry-bella", Description: "Model registry bella description", DisplayName: "Model Registry Bella"}, - }, nil + originalServices, err := m.KubernetesClient.GetServiceDetails() + if err != nil { + return nil, fmt.Errorf("failed to get service details: %w", err) + } + + for i := range originalServices { + originalServices[i].ClusterIP = "127.0.0.1" + originalServices[i].HTTPPort = 8080 + } + + return originalServices, nil +} + +func (m *KubernetesClientMock) GetServiceDetailsByName(serviceName string) (k8s.ServiceDetails, error) { + originalService, err := m.KubernetesClient.GetServiceDetailsByName(serviceName) + if err != nil { + return k8s.ServiceDetails{}, fmt.Errorf("failed to get service details: %w", err) + } + //changing from cluster service ip to localhost + originalService.ClusterIP = "127.0.0.1" + originalService.HTTPPort = 8080 + + return originalService, nil } func (m *KubernetesClientMock) BearerToken() (string, error) { return "FAKE BEARER TOKEN", nil } -func (m *KubernetesClientMock) GetServiceDetailsByName(serviceName string) (k8s.ServiceDetails, error) { - //expected forward to docker compose -f docker-compose.yaml up - return k8s.ServiceDetails{ - Name: serviceName, - ClusterIP: "127.0.0.1", - HTTPPort: 8080, - }, nil +func createService(k8sClient client.Client, ctx context.Context, name string, namespace string, displayName string, description string, clusterIP string) error { + + annotations := map[string]string{} + + if displayName != "" { + annotations["displayName"] = displayName + } + + if description != "" { + annotations["description"] = description + } + + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: annotations, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "component": k8s.ComponentName, + }, + Type: corev1.ServiceTypeClusterIP, + ClusterIP: clusterIP, + Ports: []corev1.ServicePort{ + { + Name: "http-api", + Port: 8080, + Protocol: corev1.ProtocolTCP, + AppProtocol: strPtr("http"), + }, + { + Name: "grpc-api", + Port: 9090, + Protocol: corev1.ProtocolTCP, + AppProtocol: strPtr("grpc"), + }, + }, + }, + } + + err := k8sClient.Create(ctx, service) + if err != nil { + return fmt.Errorf("failed to create services: %w", err) + } + + serviceList := &corev1.ServiceList{} + + err = k8sClient.List(ctx, serviceList, &client.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to list services: %w", err) + } + + if err != nil { + return err + } + return nil +} + +func strPtr(s string) *string { + return &s } diff --git a/clients/ui/bff/internal/mocks/k8s_mock_test.go b/clients/ui/bff/internal/mocks/k8s_mock_test.go new file mode 100644 index 00000000..9ef9f502 --- /dev/null +++ b/clients/ui/bff/internal/mocks/k8s_mock_test.go @@ -0,0 +1,62 @@ +package mocks + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Kubernetes Client Test", func() { + Context("with existing services", Ordered, func() { + + It("should retrieve the get all service successfully", func() { + + By("getting service details") + services, err := k8sClient.GetServiceDetails() + Expect(err).NotTo(HaveOccurred(), "Failed to create HTTP request") + + By("checking that all services have the modified ClusterIP and HTTPPort") + for _, service := range services { + Expect(service.ClusterIP).To(Equal("127.0.0.1"), "ClusterIP should be set to 127.0.0.1") + Expect(service.HTTPPort).To(Equal(int32(8080)), "HTTPPort should be set to 8080") + + } + + By("checking that that a specific service exists") + foundService := false + for _, service := range services { + if service.Name == "model-registry" { + foundService = true + Expect(service.DisplayName).To(Equal("Model Registry")) + Expect(service.Description).To(Equal("Model Registry Description")) + break + } + } + Expect(foundService).To(Equal(true), "Expected to find service 'model-registry'") + }) + + It("should retrieve the service details by name", func() { + + By("getting service by name") + service, err := k8sClient.GetServiceDetailsByName("model-registry-dora") + Expect(err).NotTo(HaveOccurred(), "Failed to create k8s request") + + By("checking that service details are correct") + Expect(service.Name).To(Equal("model-registry-dora")) + Expect(service.Description).To(Equal("Model Registry Dora description")) + Expect(service.DisplayName).To(Equal("Model Registry Dora")) + }) + + It("should retrieve the services names", func() { + + By("getting service by name") + services, err := k8sClient.GetServiceNames() + Expect(err).NotTo(HaveOccurred(), "Failed to create HTTP request") + + By("checking that service details are correct") + Expect(services[0]).To(Equal("model-registry")) + Expect(services[1]).To(Equal("model-registry-bella")) + Expect(services[2]).To(Equal("model-registry-dora")) + }) + }) + +}) diff --git a/clients/ui/bff/internal/mocks/suite_test.go b/clients/ui/bff/internal/mocks/suite_test.go new file mode 100644 index 00000000..14e77939 --- /dev/null +++ b/clients/ui/bff/internal/mocks/suite_test.go @@ -0,0 +1,52 @@ +package mocks + +import ( + "context" + k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations" + "log/slog" + "os" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "testing" +) + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + k8sClient k8s.KubernetesClientInterface + ctx context.Context + cancel context.CancelFunc + logger *slog.Logger + err error +) + +func TestAPI(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "API Suite") +} + +var _ = BeforeSuite(func() { + defer GinkgoRecover() + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + ctx, cancel = context.WithCancel(context.Background()) + + By("bootstrapping test environment") + logger = slog.New(slog.NewTextHandler(os.Stdout, nil)) + + k8sClient, err = NewKubernetesClient(logger, ctx, cancel) + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + defer cancel() + err := k8sClient.Shutdown(ctx, logger) + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/clients/ui/bff/internal/repositories/model_registry_test.go b/clients/ui/bff/internal/repositories/model_registry_test.go index c86a2c21..e430011c 100644 --- a/clients/ui/bff/internal/repositories/model_registry_test.go +++ b/clients/ui/bff/internal/repositories/model_registry_test.go @@ -1,26 +1,28 @@ package repositories import ( - "github.com/kubeflow/model-registry/ui/bff/internal/mocks" "github.com/kubeflow/model-registry/ui/bff/internal/models" - "github.com/stretchr/testify/assert" - "testing" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -func TestFetchAllModelRegistry(t *testing.T) { - mockK8sClient, _ := mocks.NewKubernetesClient(nil) +var _ = Describe("TestFetchAllModelRegistry", func() { + Context("with existing model registries", Ordered, func() { - mrClient := NewModelRegistryRepository() + It("should retrieve the get all service successfully", func() { - registries, err := mrClient.FetchAllModelRegistries(mockK8sClient) + By("fetching all model registries in the repository") + modelRegistryRepository := NewModelRegistryRepository() + registries, err := modelRegistryRepository.FetchAllModelRegistries(k8sClient) + Expect(err).NotTo(HaveOccurred()) - assert.NoError(t, err) - - expectedRegistries := []models.ModelRegistryModel{ - {Name: "model-registry", Description: "Model registry description", DisplayName: "Model Registry"}, - {Name: "model-registry-dora", Description: "Model registry dora description", DisplayName: "Model Registry Dora"}, - {Name: "model-registry-bella", Description: "Model registry bella description", DisplayName: "Model Registry Bella"}, - } - assert.Equal(t, expectedRegistries, registries) - -} + By("should match the expected model registries") + expectedRegistries := []models.ModelRegistryModel{ + {Name: "model-registry", Description: "Model Registry Description", DisplayName: "Model Registry"}, + {Name: "model-registry-bella", Description: "Model Registry Bella description", DisplayName: "Model Registry Bella"}, + {Name: "model-registry-dora", Description: "Model Registry Dora description", DisplayName: "Model Registry Dora"}, + } + Expect(registries).To(ConsistOf(expectedRegistries)) + }) + }) +}) diff --git a/clients/ui/bff/internal/repositories/suite_test.go b/clients/ui/bff/internal/repositories/suite_test.go new file mode 100644 index 00000000..4afd9c12 --- /dev/null +++ b/clients/ui/bff/internal/repositories/suite_test.go @@ -0,0 +1,59 @@ +package repositories + +import ( + "context" + k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations" + "github.com/kubeflow/model-registry/ui/bff/internal/mocks" + "log/slog" + "os" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "testing" +) + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + k8sClient k8s.KubernetesClientInterface + mockMRClient *mocks.ModelRegistryClientMock + ctx context.Context + cancel context.CancelFunc + logger *slog.Logger + err error +) + +func TestAPI(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "API Suite") + +} + +var _ = BeforeSuite(func() { + defer GinkgoRecover() // Ensure Ginkgo can handle any panic during setup + + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + ctx, cancel = context.WithCancel(context.Background()) + + By("bootstrapping test environment") + logger = slog.New(slog.NewTextHandler(os.Stdout, nil)) + + k8sClient, err = mocks.NewKubernetesClient(logger, ctx, cancel) + Expect(err).NotTo(HaveOccurred()) + + mockMRClient, err = mocks.NewModelRegistryClient(nil) + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := k8sClient.Shutdown(ctx, logger) + defer cancel() + Expect(err).NotTo(HaveOccurred()) +})