From 56d2a7bfaba56de73195e072c0a63347fb23f87b Mon Sep 17 00:00:00 2001 From: Katka92 Date: Tue, 26 Sep 2023 12:25:52 +0200 Subject: [PATCH] Refactor pact tests --- contracts/application_pact_test.go | 134 ------------ .../application_pact_test_state_handlers.go | 191 ------------------ contracts/pact_state_handlers_methods.go | 170 ++++++++++++++++ contracts/pact_state_params.go | 49 +++++ contracts/pact_states.go | 29 +++ contracts/pact_test.go | 142 +++++++++++++ ...ation_pact_test_utils.go => pact_utils.go} | 29 --- 7 files changed, 390 insertions(+), 354 deletions(-) delete mode 100644 contracts/application_pact_test.go delete mode 100644 contracts/application_pact_test_state_handlers.go create mode 100644 contracts/pact_state_handlers_methods.go create mode 100644 contracts/pact_state_params.go create mode 100644 contracts/pact_states.go create mode 100644 contracts/pact_test.go rename contracts/{application_pact_test_utils.go => pact_utils.go} (69%) diff --git a/contracts/application_pact_test.go b/contracts/application_pact_test.go deleted file mode 100644 index 8cb82e219..000000000 --- a/contracts/application_pact_test.go +++ /dev/null @@ -1,134 +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 contracts - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "os" - "testing" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - 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" -) - -func TestContracts(t *testing.T) { - // setup default variables for tests - HASAppNamespace := "default" - - // setup default variables for Pact - verifyRequest := provider.VerifyRequest{ - Provider: "HAS", - RequestTimeout: 60 * time.Second, - // Default selector should include environments, but as they are not in place yet, using just main branch - // ConsumerVersionSelectors: []pactTypes.ConsumerVersionSelector{{Branch: "main"}, {Environment: "stage"}, {Environment: "production"}}, - ConsumerVersionSelectors: []provider.Selector{&provider.ConsumerVersionSelector{Branch: "main"}}, - ProviderVersion: os.Getenv("COMMIT_SHA"), - BrokerURL: "https://pact-broker-hac-pact-broker.apps.hac-devsandbox.5unc.p1.openshiftapps.com", - PublishVerificationResults: false, - BrokerUsername: "pactCommonUser", - BrokerPassword: "pactCommonPassword123", // notsecret - } - - if os.Getenv("SKIP_PACT_TESTS") == "true" { - t.Skip("Skipping Pact tests as SKIP_PACT_TESTS is set to true.") - } - - // setup credentials and publishing - if os.Getenv("PR_CHECK") == "true" { - if os.Getenv("COMMIT_SHA") == "" { - t.Skip("Skipping Pact tests from unit test suite during PR check.") - } else { - t.Log("Running Pact tests from a PR check. Verifying against main branch and all environments, not pushing results to broker.") - } - } else { - if os.Getenv("PACT_BROKER_USERNAME") == "" { - t.Log("Running tests locally. Verifying against main branch, not pushing results to broker.") - verifyRequest.ProviderVersion = "local" - verifyRequest.ConsumerVersionSelectors = []provider.Selector{&provider.ConsumerVersionSelector{Branch: "main"}} - - // For running localy and testing Pacts from a locall directory: - // - uncomment pactDir variable and set it to the folder with pacts - // - uncomment verifyRequest.PactURLs - // - comment out all Broker* variables (BrokerUsername, BrokerPassword, BrokerURL) - // var pactDir = "/home/usr/pacts" - // verifyRequest.PactFiles = []string{filepath.ToSlash(fmt.Sprintf("%s/HACdev-HAS.json", pactDir))} - } else { - t.Log("Running tests post-merge. Verifying against main branch and all environments. Pushing results to Pact broker with the branch \"main\".") - verifyRequest.BrokerUsername = os.Getenv("PACT_BROKER_USERNAME") - verifyRequest.BrokerPassword = os.Getenv("PACT_BROKER_PASSWORD") - verifyRequest.ProviderBranch = os.Getenv("PROVIDER_BRANCH") - verifyRequest.PublishVerificationResults = true - } - } - - // Register fail handler and setup test environment (same as during unit tests) - RegisterFailHandler(Fail) - k8sClient, testEnv, ctx, cancel = controllers.SetupTestEnv() - - verifyRequest.ProviderBaseURL = testEnv.Config.Host - - // Certificate magic - for the mocked service to be able to communicate with kube-apiserver & for authorization - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(testEnv.Config.CAData) - certs, err := tls.X509KeyPair(testEnv.Config.CertData, testEnv.Config.KeyData) - if err != nil { - panic(err) - } - tlsConfig := &tls.Config{ - RootCAs: caCertPool, - Certificates: []tls.Certificate{certs}, - } - verifyRequest.CustomTLSConfig = tlsConfig - // End of certificate magic - - // 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 }, - // 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 - k8sClient.DeleteAllOf(context.Background(), &appstudiov1alpha1.Application{}, client.InNamespace(HASAppNamespace)) - k8sClient.DeleteAllOf(context.Background(), &appstudiov1alpha1.Component{}, client.InNamespace(HASAppNamespace)) - return nil - } - - // Run pact tests - err = provider.NewVerifier().VerifyProvider(t, verifyRequest) - if err != nil { - t.Errorf("Error while verifying tests. \n %+v", err) - } - - cancel() - err = testEnv.Stop() - if err != nil { - fmt.Println("Stopping failed") - fmt.Printf("%+v", err) - panic("Cleanup failed") - } -} diff --git a/contracts/application_pact_test_state_handlers.go b/contracts/application_pact_test_state_handlers.go deleted file mode 100644 index 71f9c644d..000000000 --- a/contracts/application_pact_test_state_handlers.go +++ /dev/null @@ -1,191 +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 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/contracts/pact_state_handlers_methods.go b/contracts/pact_state_handlers_methods.go new file mode 100644 index 000000000..9f6cde490 --- /dev/null +++ b/contracts/pact_state_handlers_methods.go @@ -0,0 +1,170 @@ +// +// 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" + "os" + "reflect" + "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" + + core "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "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 +) + +const timeout = 10 * time.Second +const interval = 250 * time.Millisecond + +func createApp(setup bool, state models.ProviderState) (models.ProviderStateResponse, error) { + if !setup { + err := os.Setenv("SETUP", "false") + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + return nil, nil + } + err := os.Setenv("SETUP", "true") + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + params := parseApplication(state.Parameters) + hasApp := getApplicationSpec(params.appName, params.namespace) + hasAppLookupKey := types.NamespacedName{Name: params.appName, Namespace: params.namespace} + createdHasApp := &appstudiov1alpha1.Application{} + + // create app + gomega.Expect(k8sClient.Create(ctx, hasApp)).Should(gomega.Succeed()) + + // check it is created + gomega.Eventually(func() bool { + err := k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + return len(createdHasApp.Status.Conditions) > 0 + }, timeout, interval).Should(gomega.BeTrue()) + + return nil, nil + +} + +func createComponents(setup bool, state models.ProviderState) (models.ProviderStateResponse, error) { + if !setup { + err := os.Setenv("SETUP", "false") + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + return nil, nil + } + err := os.Setenv("SETUP", "true") + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + components := parseComponents(state.Parameters) + for _, comp := range 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{} + + // wait until component is created + gomega.Eventually(func() bool { + err := k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + return len(createdHasComp.Status.Conditions) > 1 + }, timeout, interval).Should(gomega.BeTrue()) + + // wait until component is ready + gomega.Eventually(func() bool { + err := k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, comp.name) + }, timeout, interval).Should(gomega.BeTrue()) + } + return nil, nil + +} + +func removeAllInstacesInNamespace(namespace string, myInstance client.Object) { + objectKind := strings.Split(reflect.TypeOf(myInstance).String(), ".")[1] + remainingCount := getObjectCountInNamespace(objectKind, namespace) + if remainingCount == 0 { + return + } + // remove resources in namespace + err := k8sClient.DeleteAllOf(context.Background(), myInstance, client.InNamespace(namespace)) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + // watch number of resources existing + gomega.Eventually(func() bool { + objectKind := strings.Split(reflect.TypeOf(myInstance).String(), ".")[1] + remainingCount := getObjectCountInNamespace(objectKind, namespace) + println("Removing", objectKind, "instances from", namespace, "namespace. Remaining: ", remainingCount) + return remainingCount == 0 + }, timeout, interval).Should(gomega.BeTrue()) +} + +func getObjectCountInNamespace(objectKind string, namespace string) int { + unstructuredObject := &unstructured.Unstructured{} + + unstructuredObject.SetGroupVersionKind(schema.GroupVersionKind{ + Group: appstudiov1alpha1.GroupVersion.Group, + Version: appstudiov1alpha1.GroupVersion.Version, + Kind: objectKind, + }) + + err := k8sClient.List(context.Background(), unstructuredObject, &client.ListOptions{Namespace: namespace}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + listOfObjects, _ := unstructuredObject.ToList() + return len(listOfObjects.Items) +} + +func cleanUpNamespaces() { + println("clean up namespaces") + removeAllInstances(&appstudiov1alpha1.Component{}) + removeAllInstances(&appstudiov1alpha1.Application{}) + removeAllInstances(&appstudiov1alpha1.ComponentDetectionQuery{}) +} + +// remove all instances of the given type within the whole cluster +func removeAllInstances(myInstance client.Object) { + listOfNamespaces := getListOfNamespaces() + for _, item := range listOfNamespaces.Items { + removeAllInstacesInNamespace(item.Name, myInstance) + } +} + +// return all namespaces where the instances of the specified object kind exist +func getListOfNamespaces() core.NamespaceList { + namespaceList := &core.NamespaceList{} + err := k8sClient.List(context.Background(), namespaceList, &client.ListOptions{Namespace: ""}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + return *namespaceList +} diff --git a/contracts/pact_state_params.go b/contracts/pact_state_params.go new file mode 100644 index 000000000..dcb44f02e --- /dev/null +++ b/contracts/pact_state_params.go @@ -0,0 +1,49 @@ +// +// 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 + +// This should correspond with the https://github.com/openshift/hac-dev/blob/main/pact-tests/states/state-params.ts + +type ComponentParams struct { + app ApplicationParams + repo string + name string +} + +type ApplicationParams struct { + appName string + namespace string +} + +func parseApplication(params map[string]interface{}) ApplicationParams { + return ApplicationParams{ + params["params"].(map[string]interface{})["appName"].(string), + params["params"].(map[string]interface{})["namespace"].(string), + } +} + +func parseComponents(params map[string]interface{}) []ComponentParams { + tmp := params["params"].(map[string]interface{})["components"].([]interface{}) + var components []ComponentParams + for _, compToParse := range tmp { + component := compToParse.(map[string]interface{}) + appParsed := ApplicationParams{component["app"].(map[string]interface{})["appName"].(string), + component["app"].(map[string]interface{})["namespace"].(string)} + compParsed := ComponentParams{appParsed, component["repo"].(string), component["compName"].(string)} + components = append(components, compParsed) + } + return components +} diff --git a/contracts/pact_states.go b/contracts/pact_states.go new file mode 100644 index 000000000..474cf2424 --- /dev/null +++ b/contracts/pact_states.go @@ -0,0 +1,29 @@ +// +// 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 + +// The list of used states and their params can be found there: https://github.com/openshift/hac-dev/blob/main/pact-tests/states/states.ts#L18 + +import ( + models "github.com/pact-foundation/pact-go/v2/models" +) + +func setupStateHandler() models.StateHandlers { + return models.StateHandlers{ + "Application exists": createApp, + "Application has components": createComponents, + } +} diff --git a/contracts/pact_test.go b/contracts/pact_test.go new file mode 100644 index 000000000..ec34a0f58 --- /dev/null +++ b/contracts/pact_test.go @@ -0,0 +1,142 @@ +// +// 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 ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + provider "github.com/pact-foundation/pact-go/v2/provider" + "github.com/redhat-appstudio/application-service/controllers" +) + +func TestContracts(t *testing.T) { + // Skip tests if "SKIP_PACT_TESTS" env var is set + // or if the unit tests are running during PR check job + if os.Getenv("SKIP_PACT_TESTS") == "true" || (os.Getenv("PR_CHECK") == "true" && os.Getenv("COMMIT_SHA") == "") { + t.Skip("Skipping Pact tests.") + } + + // Register fail handler and setup test environment (same as during unit tests) + RegisterFailHandler(Fail) + k8sClient, testEnv, ctx, cancel = controllers.SetupTestEnv() + + // Create and setup Pact Verifier + verifyRequest := createVerifier(t) + + // Run pact tests + err := provider.NewVerifier().VerifyProvider(t, verifyRequest) + if err != nil { + t.Errorf("Error while verifying tests. \n %+v", err) + } + + println("cleanup") + cleanUpNamespaces() + + cancel() + + err = testEnv.Stop() + if err != nil { + fmt.Println("Stopping failed") + fmt.Printf("%+v", err) + panic("Cleanup failed") + } +} + +func createVerifier(t *testing.T) provider.VerifyRequest { + brokerUsername, _ := base64.StdEncoding.DecodeString("cGFjdENvbW1vblVzZXI=") + brokerPassword, _ := base64.StdEncoding.DecodeString("cGFjdENvbW1vblBhc3N3b3JkMTIz") + verifyRequest := provider.VerifyRequest{ + Provider: "HAS", + RequestTimeout: 60 * time.Second, + ProviderBaseURL: testEnv.Config.Host, + // Default selector should include environments, but as they are not in place yet, using just main branch + ConsumerVersionSelectors: []provider.Selector{&provider.ConsumerVersionSelector{Branch: "main"}}, + BrokerURL: "https://pact-broker-hac-pact-broker.apps.hac-devsandbox.5unc.p1.openshiftapps.com", + PublishVerificationResults: false, + BrokerUsername: string(brokerUsername), + BrokerPassword: string(brokerPassword), + EnablePending: true, + ProviderVersion: "local", + ProviderBranch: "main", + } + + // clean up test env before every test + verifyRequest.BeforeEach = func() error { + // workaround for https://github.com/pact-foundation/pact-go/issues/359 + if os.Getenv("SETUP") == "true" { + return nil + } + cleanUpNamespaces() + return nil + } + + // setup credentials and publishing + if os.Getenv("PR_CHECK") != "true" { + if os.Getenv("PACT_BROKER_USERNAME") == "" { + // To run Pact tests against local contract files, set LOCAL_PACT_FILES_FOLDER to the folder with pact jsons + var pactDir, useLocalFiles = os.LookupEnv("LOCAL_PACT_FILES_FOLDER") + if useLocalFiles { + verifyRequest.BrokerPassword = "" + verifyRequest.BrokerUsername = "" + verifyRequest.BrokerURL = "" + verifyRequest.PactFiles = []string{filepath.ToSlash(fmt.Sprintf("%s/HACdev-HAS.json", pactDir))} + t.Log("Running tests locally. Verifying tests from local folder: ", pactDir) + } else { + t.Log("Running tests locally. Verifying against main branch, not pushing results to broker.") + // verifyRequest.ConsumerVersionSelectors = []provider.Selector{&provider.ConsumerVersionSelector{Branch: "main"}} + // to test against changes in specific HAC-dev PR, use Tag: + verifyRequest.ConsumerVersionSelectors = []provider.Selector{&provider.ConsumerVersionSelector{Tag: "PR808", Latest: true}} + } + } else { + t.Log("Running tests post-merge. Verifying against main branch and all environments. Pushing results to Pact broker with the branch \"main\".") + verifyRequest.BrokerUsername = os.Getenv("PACT_BROKER_USERNAME") + verifyRequest.BrokerPassword = os.Getenv("PACT_BROKER_PASSWORD") + verifyRequest.ProviderBranch = os.Getenv("PROVIDER_BRANCH") + verifyRequest.ProviderVersion = os.Getenv("COMMIT_SHA") + verifyRequest.PublishVerificationResults = true + } + } + + // setup state handlers + verifyRequest.StateHandlers = setupStateHandler() + + // Certificate magic - for the mocked service to be able to communicate with kube-apiserver & for authorization + verifyRequest.CustomTLSConfig = createTlsConfig() + + return verifyRequest +} + +func createTlsConfig() *tls.Config { + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(testEnv.Config.CAData) + certs, err := tls.X509KeyPair(testEnv.Config.CertData, testEnv.Config.KeyData) + if err != nil { + panic(err) + } + return &tls.Config{ + RootCAs: caCertPool, + Certificates: []tls.Certificate{certs}, + } +} diff --git a/contracts/application_pact_test_utils.go b/contracts/pact_utils.go similarity index 69% rename from contracts/application_pact_test_utils.go rename to contracts/pact_utils.go index d68b3f058..a81578d98 100644 --- a/contracts/application_pact_test_utils.go +++ b/contracts/pact_utils.go @@ -67,32 +67,3 @@ func getGhComponentSpec(name string, namespace string, appname string, repo stri }, } } - -func getQuayComponentSpec(name string, namespace string, appname string, repo string) *appstudiov1alpha1.Component { - SampleRepoLink := "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" - return &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: name, - Application: appname, - ContainerImage: repo, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - }, - }, - }, - Replicas: &replicas, - TargetPort: 1111, - Route: "route-endpoint-url", - }, - } -}