From c2f7c296dc3a7732b3fd2d16d9f50685935125ad Mon Sep 17 00:00:00 2001 From: Katerina Foniok Date: Mon, 25 Sep 2023 13:19:24 +0200 Subject: [PATCH] Refactoring Pact tests and adding parametrized states (#391) * Move Pact contract tests related files to a separate package * Remove sleeps --- .../application_pact_test.go | 10 +- .../application_pact_test_state_handlers.go | 191 ++++++++++++++++++ .../application_pact_test_utils.go | 2 +- .../application_pact_test_state_handlers.go | 93 --------- controllers/start_test_env.go | 3 +- controllers/suite_test.go | 4 +- 6 files changed, 204 insertions(+), 99 deletions(-) rename {controllers => contracts}/application_pact_test.go (94%) create mode 100644 contracts/application_pact_test_state_handlers.go rename {controllers => contracts}/application_pact_test_utils.go (99%) delete mode 100644 controllers/application_pact_test_state_handlers.go diff --git a/controllers/application_pact_test.go b/contracts/application_pact_test.go similarity index 94% rename from controllers/application_pact_test.go rename to contracts/application_pact_test.go index a334fe65..05dd82f2 100644 --- a/controllers/application_pact_test.go +++ b/contracts/application_pact_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package controllers +package contracts import ( "context" @@ -29,6 +29,7 @@ import ( models "github.com/pact-foundation/pact-go/v2/models" provider "github.com/pact-foundation/pact-go/v2/provider" appstudiov1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1" + "github.com/redhat-appstudio/application-service/controllers" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -84,7 +85,7 @@ func TestContracts(t *testing.T) { // Register fail handler and setup test environment (same as during unit tests) RegisterFailHandler(Fail) - setupTestEnv() + k8sClient, testEnv, ctx, cancel = controllers.SetupTestEnv() verifyRequest.ProviderBaseURL = testEnv.Config.Host @@ -105,7 +106,10 @@ func TestContracts(t *testing.T) { // setup state handlers verifyRequest.StateHandlers = models.StateHandlers{ "No app with the name app-to-create in the default namespace exists.": func(setup bool, s models.ProviderState) (models.ProviderStateResponse, error) { return nil, nil }, - "App myapp exists and has component gh-component and quay-component": createAppAndComponents(HASAppNamespace), + // deprecated + "App myapp exists and has component gh-component and quay-component": createAppAndComponents(HASAppNamespace), + "Application exists": createApp(), + "Application has components": createComponents(), } verifyRequest.AfterEach = func() error { // Remove all applications and components after each tests diff --git a/contracts/application_pact_test_state_handlers.go b/contracts/application_pact_test_state_handlers.go new file mode 100644 index 00000000..71f9c644 --- /dev/null +++ b/contracts/application_pact_test_state_handlers.go @@ -0,0 +1,191 @@ +// +// Copyright 2023 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package contracts + +import ( + "context" + "strings" + "time" + + gomega "github.com/onsi/gomega" + models "github.com/pact-foundation/pact-go/v2/models" + appstudiov1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +var ( + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc +) + +type Comp struct { + app AppParams + repo string + name string +} + +type CompParams struct { + components []Comp +} + +type AppParams struct { + appName string + namespace string +} + +const timeout = 10 * time.Second +const interval = 250 * time.Millisecond + +// Deprecated +func createAppAndComponents(HASAppNamespace string) models.StateHandler { + var stateHandler = func(setup bool, s models.ProviderState) (models.ProviderStateResponse, error) { + if !setup { + println("skipping state handler") + return nil, nil + } + + appName := "myapp" + ghCompName := "gh-component" + quayCompName := "quay-component" + ghCompRepoLink := "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" + quayRepoLink := "quay.io/test/test-image:latest" + + hasApp := getApplicationSpec(appName, HASAppNamespace) + ghComp := getGhComponentSpec(ghCompName, HASAppNamespace, appName, ghCompRepoLink) + quayComp := getQuayComponentSpec(quayCompName, HASAppNamespace, appName, quayRepoLink) + + //create app + gomega.Expect(k8sClient.Create(ctx, hasApp)).Should(gomega.Succeed()) + hasAppLookupKey := types.NamespacedName{Name: appName, Namespace: HASAppNamespace} + createdHasApp := &appstudiov1alpha1.Application{} + gomega.Eventually(func() bool { + k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) + if len(createdHasApp.Status.Conditions) > 0 { + return createdHasApp.Status.Conditions[0].Type == "Created" + } + return false + }, timeout, interval).Should(gomega.BeTrue()) + + //create gh component + gomega.Expect(k8sClient.Create(ctx, ghComp)).Should(gomega.Succeed()) + hasCompLookupKey := types.NamespacedName{Name: ghCompName, Namespace: HASAppNamespace} + createdHasComp := &appstudiov1alpha1.Component{} + gomega.Eventually(func() bool { + gomega.Expect(k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp)).Should(gomega.Succeed()) + return len(createdHasComp.Status.Conditions) > 1 + }, timeout, interval).Should(gomega.BeTrue()) + + //create quay component + gomega.Expect(k8sClient.Create(ctx, quayComp)).Should(gomega.Succeed()) + hasCompLookupKey2 := types.NamespacedName{Name: quayCompName, Namespace: HASAppNamespace} + createdHasComp2 := &appstudiov1alpha1.Component{} + gomega.Eventually(func() bool { + gomega.Expect(k8sClient.Get(context.Background(), hasCompLookupKey2, createdHasComp2)).Should(gomega.Succeed()) + return len(createdHasComp2.Status.Conditions) > 1 + }, timeout, interval).Should(gomega.BeTrue()) + + gomega.Eventually(func() bool { + gomega.Expect(k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp)).Should(gomega.Succeed()) + return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ghCompName) + }, timeout, interval).Should(gomega.BeTrue()) + return nil, nil + } + return stateHandler +} + +func createApp() models.StateHandler { + var stateHandler = func(setup bool, s models.ProviderState) (models.ProviderStateResponse, error) { + if !setup { + println("skipping state handler during turndownn phase") + return nil, nil + } + + app := parseApp(s.Parameters) + hasApp := getApplicationSpec(app.appName, app.namespace) + + //create app + gomega.Expect(k8sClient.Create(ctx, hasApp)).Should(gomega.Succeed()) + hasAppLookupKey := types.NamespacedName{Name: app.appName, Namespace: app.namespace} + createdHasApp := &appstudiov1alpha1.Application{} + + gomega.Eventually(func() bool { + k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) + return len(createdHasApp.Status.Conditions) > 0 + }, timeout, interval).Should(gomega.BeTrue()) + + return nil, nil + } + return stateHandler +} + +func createComponents() models.StateHandler { + var stateHandler = func(setup bool, s models.ProviderState) (models.ProviderStateResponse, error) { + if !setup { + println("skipping state handler") + return nil, nil + } + + components := parseComp(s.Parameters) + + for _, comp := range components.components { + ghComp := getGhComponentSpec(comp.name, comp.app.namespace, comp.app.appName, comp.repo) + + hasAppLookupKey := types.NamespacedName{Name: comp.app.appName, Namespace: comp.app.namespace} + createdHasApp := &appstudiov1alpha1.Application{} + + //create gh component + gomega.Expect(k8sClient.Create(ctx, ghComp)).Should(gomega.Succeed()) + hasCompLookupKey := types.NamespacedName{Name: comp.name, Namespace: comp.app.namespace} + createdHasComp := &appstudiov1alpha1.Component{} + gomega.Eventually(func() bool { + k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) + return len(createdHasComp.Status.Conditions) > 1 + }, timeout, interval).Should(gomega.BeTrue()) + + gomega.Eventually(func() bool { + k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) + return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, comp.name) + }, timeout, interval).Should(gomega.BeTrue()) + } + return nil, nil + } + return stateHandler +} + +func parseApp(params map[string]interface{}) AppParams { + return AppParams{ + params["params"].(map[string]interface{})["appName"].(string), + params["params"].(map[string]interface{})["namespace"].(string), + } +} + +func parseComp(params map[string]interface{}) CompParams { + tmp := params["params"].(map[string]interface{})["components"].([]interface{}) + var components CompParams + for _, compToParse := range tmp { + component := compToParse.(map[string]interface{}) + appParsed := AppParams{component["app"].(map[string]interface{})["appName"].(string), + component["app"].(map[string]interface{})["namespace"].(string)} + compParsed := Comp{appParsed, component["repo"].(string), component["compName"].(string)} + components.components = append(components.components, compParsed) + } + return components +} diff --git a/controllers/application_pact_test_utils.go b/contracts/application_pact_test_utils.go similarity index 99% rename from controllers/application_pact_test_utils.go rename to contracts/application_pact_test_utils.go index 449a209d..d68b3f05 100644 --- a/controllers/application_pact_test_utils.go +++ b/contracts/application_pact_test_utils.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package controllers +package contracts import ( appstudiov1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1" diff --git a/controllers/application_pact_test_state_handlers.go b/controllers/application_pact_test_state_handlers.go deleted file mode 100644 index 1e680f7b..00000000 --- a/controllers/application_pact_test_state_handlers.go +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -import ( - "context" - "strings" - "time" - - gomega "github.com/onsi/gomega" - models "github.com/pact-foundation/pact-go/v2/models" - appstudiov1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1" - "k8s.io/apimachinery/pkg/types" -) - -func createAppAndComponents(HASAppNamespace string) models.StateHandler { - var stateHandler = func(setup bool, s models.ProviderState) (models.ProviderStateResponse, error) { - if !setup { - println("skipping state handler") - return nil, nil - } - - appName := "myapp" - ghCompName := "gh-component" - quayCompName := "quay-component" - ghCompRepoLink := "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" - quayRepoLink := "quay.io/test/test-image:latest" - - hasApp := getApplicationSpec(appName, HASAppNamespace) - ghComp := getGhComponentSpec(ghCompName, HASAppNamespace, appName, ghCompRepoLink) - quayComp := getQuayComponentSpec(quayCompName, HASAppNamespace, appName, quayRepoLink) - - //create app - gomega.Expect(k8sClient.Create(ctx, hasApp)).Should(gomega.Succeed()) - hasAppLookupKey := types.NamespacedName{Name: appName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - for i := 0; i < 12; i++ { - gomega.Expect(k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp)).Should(gomega.Succeed()) - if len(createdHasApp.Status.Conditions) > 0 { - if createdHasApp.Status.Conditions[0].Type == "Created" { - break - } - } - time.Sleep(10 * time.Second) - } - - //create gh component - gomega.Expect(k8sClient.Create(ctx, ghComp)).Should(gomega.Succeed()) - hasCompLookupKey := types.NamespacedName{Name: ghCompName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - for i := 0; i < 12; i++ { - gomega.Expect(k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp)).Should(gomega.Succeed()) - if len(createdHasComp.Status.Conditions) > 1 { - break - } - time.Sleep(10 * time.Second) - } - //create quay component - gomega.Expect(k8sClient.Create(ctx, quayComp)).Should(gomega.Succeed()) - hasCompLookupKey2 := types.NamespacedName{Name: quayCompName, Namespace: HASAppNamespace} - createdHasComp2 := &appstudiov1alpha1.Component{} - for i := 0; i < 12; i++ { - gomega.Expect(k8sClient.Get(context.Background(), hasCompLookupKey2, createdHasComp2)).Should(gomega.Succeed()) - if len(createdHasComp2.Status.Conditions) > 1 { - break - } - time.Sleep(10 * time.Second) - } - - for i := 0; i < 12; i++ { - gomega.Expect(k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp)).Should(gomega.Succeed()) - if len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ghCompName) { - break - } - time.Sleep(10 * time.Second) - } - return nil, nil - } - return stateHandler -} diff --git a/controllers/start_test_env.go b/controllers/start_test_env.go index 155ea1ea..e67ccecc 100644 --- a/controllers/start_test_env.go +++ b/controllers/start_test_env.go @@ -51,7 +51,7 @@ var ( cancel context.CancelFunc ) -func setupTestEnv() { +func SetupTestEnv() (client.Client, *envtest.Environment, context.Context, context.CancelFunc) { logf.SetLogger(zap.New(zap.WriteTo(ginkgo.GinkgoWriter), zap.UseDevMode(true))) ctx, cancel = context.WithCancel(context.TODO()) @@ -140,4 +140,5 @@ func setupTestEnv() { err = k8sManager.Start(ctx) gomega.Expect(err).ToNot(gomega.HaveOccurred(), "failed to run manager") }() + return k8sClient, testEnv, ctx, cancel } diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 68e67939..44e94bf2 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -34,7 +34,9 @@ func TestAPIs(t *testing.T) { "Controller Suite") } -var _ = BeforeSuite(setupTestEnv, 60) +var _ = BeforeSuite(func() { + SetupTestEnv() +}, 60) var _ = AfterSuite(func() { cancel()