package integration_test import ( "context" "encoding/base64" "encoding/json" "fmt" "time" pubSub "cloud.google.com/go/pubsub" "github.com/thoas/go-funk" "github.com/udacity/graphb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "edge-infra.dev/pkg/edge/api/apierror" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/services" bannerApi "edge-infra.dev/pkg/edge/apis/banner/v1alpha1" clusterApi "edge-infra.dev/pkg/edge/apis/cluster/v1alpha1" gkeClusterApi "edge-infra.dev/pkg/edge/apis/gkecluster/v1alpha1" chariotAPI "edge-infra.dev/pkg/edge/chariot/client" "edge-infra.dev/pkg/edge/controllers/util/edgedb" chariotClientApiTestutils "edge-infra.dev/test/framework/gcp/pubsub" "edge-infra.dev/test/framework/integration" ) func (s *Suite) TestCreateBanner() { integration.SkipIf(s.Framework) var response struct{ CreateBanner *model.EdgeResponsePayload } mutation := createBannerMutation("test-create-org", false, "") ResolverClient.MustPost(mutation, &response) s.NotNil(response.CreateBanner) s.Equal(200, response.CreateBanner.StatusCode) s.Equal("Successfully created test-create-org", response.CreateBanner.Message) } func (s *Suite) TestCreateBannerWithExisitingOrg() { integration.SkipIf(s.Framework) var response struct{ CreateBanner *model.EdgeResponsePayload } mutation := createBannerMutation("test-create-with-exisiting-org", true, "3ea81a9de9564c3584015b98dd8bcfe9") ResolverClient.MustPost(mutation, &response) s.NotNil(response.CreateBanner) s.Equal(200, response.CreateBanner.StatusCode) } func (s *Suite) TestCreateBannerWithExisitingEu() { integration.SkipIf(s.Framework) var response struct{ CreateBanner *model.EdgeResponsePayload } mutation := createBannerMutation("test-create-eu", false, "cc5c02f7289343cd9c68c879ffb11c85") ResolverClient.MustPost(mutation, &response) s.NotNil(response.CreateBanner) s.Equal(200, response.CreateBanner.StatusCode) } func (s *Suite) TestCreateBannerWithDeletedEu() { integration.SkipIf(s.Framework) var response struct{ CreateBanner *model.EdgeResponsePayload } mutation := createBannerMutation("test-create-eu", false, "test-bsl-id-for-deleted-eu") err := ResolverClient.Post(mutation, &response) fmt.Println(err.Error()) s.Error(err) s.Contains(err.Error(), "enterprise unit test-bsl-id-for-deleted-eu is deleted") s.Nil(response.CreateBanner) } func (s *Suite) TestCreateBannerWithDeletedOrg() { integration.SkipIf(s.Framework) var response struct{ CreateBanner *model.EdgeResponsePayload } mutation := createBannerMutation("test-create-org", true, "test-bsl-id-for-deleted-org") err := ResolverClient.Post(mutation, &response) s.Error(err) s.Contains(err.Error(), "bsl subOrg test-bsl-id-for-deleted-org is deleted") s.Nil(response.CreateBanner) } func (s *Suite) TestUpdateBanner() { integration.SkipIf(s.Framework) var response struct{ UpdateBanner *model.EdgeResponsePayload } mutation := updateBannerMutation("3396a52c-6a22-4049-9593-5a63b596a100", "new-name", "new-desc") ResolverClient.MustPost(mutation, &response) s.NotNil(response.UpdateBanner) s.Equal(200, response.UpdateBanner.StatusCode) s.Equal("Successfully updated new-name", response.UpdateBanner.Message) // revert update to fix mismatches in bsl and sql banners mutation = updateBannerMutation("3396a52c-6a22-4049-9593-5a63b596a100", "test-org", "test_description") ResolverClient.MustPost(mutation, &response) s.NotNil(response.UpdateBanner) s.Equal(200, response.UpdateBanner.StatusCode) s.Equal("Successfully updated test-org", response.UpdateBanner.Message) } func (s *Suite) TestCreateBannerError() { integration.SkipIf(s.Framework) var response struct{ CreateBanner *model.EdgeResponsePayload } mutation := createBannerMutation("test-create-org-with-error", false, "") s.NoError(ResolverClient.Post(mutation, &response)) mutation = createBannerMutation("test-create-org-with-error", false, "") s.Error(ResolverClient.Post(mutation, &response)) s.Nil(response.CreateBanner) } func (s *Suite) TestDeleteBanner() { integration.SkipIf(s.Framework) if !integration.IsIntegrationTest() { // might want to run this as integration test in the future s.NoError(s.client.Create(context.Background(), getTestProject("test-delete-org"))) } var deleteOrganizationResponse struct{ DeleteBanner *model.EdgeResponsePayload } mutation := deleteBannerMutation("3396a52c-6a22-4049-9593-5a63b596a103") done := make(chan bool) messageCount := 0 chariotPubSubValidationFN := func(msg *pubSub.Message) { message := &chariotAPI.ChariotMessage{} err := json.Unmarshal(msg.Data, message) if err != nil { fmt.Println(err) } fmt.Printf("Special Chariot Message: %v\n", message) for _, t := range message.Objects { rawDecodedText, _ := base64.StdEncoding.DecodeString(t) resource := &unstructured.Unstructured{} _ = resource.UnmarshalJSON(rawDecodedText) if resource.GetKind() != "Cluster" && resource.GetKind() != "Banner" { s.FailNow("Chariot message not a banner or cluster") } s.Equal("DELETE", message.Operation) messageCount++ } } ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { err := chariotClientApiTestutils.CreateMockSubscription(ctx, done, topLevelProjectID, testChariotPubsubTopic, "tsub", 20*time.Second, "", chariotPubSubValidationFN, s.ChariotClient) s.NoError(err) }() ResolverClient.MustPost(mutation, &deleteOrganizationResponse) s.NotNil(deleteOrganizationResponse.DeleteBanner) s.Equal(200, deleteOrganizationResponse.DeleteBanner.StatusCode) s.Equal("Successfully deleted delete-banner", deleteOrganizationResponse.DeleteBanner.Message) //wait for two messages to be sent delete banner and delete infra select { case <-done: case <-time.After(2 * time.Second): s.Fail("Failed to send delete message") } select { case <-done: case <-time.After(2 * time.Second): s.Fail("Failed to send delete message") } s.Equal(2, messageCount) } func (s *Suite) TestDeleteBanner_NotExists() { integration.SkipIf(s.Framework) var deleteOrganizationResponse struct{ DeleteOrganization *model.EdgeResponsePayload } mutation := deleteBannerMutation("test-org-not-exist-id") s.Error(ResolverClient.Post(mutation, &deleteOrganizationResponse)) s.Nil(deleteOrganizationResponse.DeleteOrganization) } func (s *Suite) TestBanner() { integration.SkipIf(s.Framework) query := getBannerQuery("98ef1fcb-dc88-4c9f-9980-c09a04564a48") var response struct{ Banner *model.Banner } err := ResolverClient.Post(query, &response) s.NoError(err) s.Equal(response.Banner.BannerBSLId, "eu-banner-bsl-id") s.Equal(response.Banner.Name, "test-banner-eu") s.NotNil(response.Banner.BannerStatus) s.NotNil(*response.Banner.BslDataSynced) s.Equal(2, len(response.Banner.BslEntityTypes)) s.True(response.Banner.OptInEdgeSecurityCompliance, "edge-opt-in-compliance is expected to be enabled") s.ElementsMatch([]string{"item", "item-price"}, response.Banner.BslEntityTypes) } func (s *Suite) TestBanner_Mismatches() { integration.SkipIf(s.Framework) query := getBannerQuery("3396a52c-6a22-4049-9593-5a63b596a101") var response struct{ Banner *model.Banner } err := ResolverClient.Post(query, &response) s.Contains(err.Error(), apierror.BannerCheckMessage) s.Contains(err.Error(), "banner test-org-banner not found in bsl") s.Equal(response.Banner.BannerBSLId, "test-org-banner") s.Equal(response.Banner.Name, "test-org-banner") s.False(response.Banner.OptInEdgeSecurityCompliance, "edge-opt-into-compliance is expected to be disabled") } func (s *Suite) TestBanners() { integration.SkipIf(s.Framework) query := getBannersQuery() var response struct{ Banners []*model.Banner } err := ResolverClient.Post(query, &response) s.NoError(err) s.NotZero(len(response.Banners)) s.NotNil(funk.Find(response.Banners, func(o *model.Banner) bool { return o.Name == testOrg })) } func (s *Suite) TestBanners_Mismatches() { integration.SkipIf(s.Framework) var updateResp struct{ UpdateBanner *model.EdgeResponsePayload } mutation := updateBannerMutation("3396a52c-6a22-4049-9593-5a63b596a100", "new-name", "new-desc") ResolverClient.MustPost(mutation, &updateResp) s.NotNil(updateResp.UpdateBanner) s.Equal(200, updateResp.UpdateBanner.StatusCode) s.Equal("Successfully updated new-name", updateResp.UpdateBanner.Message) query := getBannersQuery() var response struct{ Banners []*model.Banner } err := ResolverClient.Post(query, &response) s.NoError(err) s.NotZero(len(response.Banners)) s.NotNil(funk.Find(response.Banners, func(o *model.Banner) bool { return o.Name == testOrg })) // revert update to fix mismatches in bsl and sql banners mutation = updateBannerMutation("3396a52c-6a22-4049-9593-5a63b596a100", "test-org", "test_description") ResolverClient.MustPost(mutation, &updateResp) s.NotNil(updateResp.UpdateBanner) s.Equal(200, updateResp.UpdateBanner.StatusCode) s.Equal("Successfully updated test-org", updateResp.UpdateBanner.Message) } func (s Suite) TestBannerStatus() { integration.SkipIf(s.Framework) // Query for banner with 'READY' status query := bannerStatusQuery("98ef1fcb-dc88-4c9f-9980-c09a04564a48") var response struct{ BannerStatus *model.BannerStatus } err := ResolverClient.Post(query, &response) s.NoError(err) s.NotNil(response.BannerStatus) s.Equal(services.ProvisionedStatus, response.BannerStatus.Reason) s.Equal("ProvisionSucceeded: banner reconciled successfully", response.BannerStatus.Message) s.True(response.BannerStatus.Ready) s.NotEmpty(response.BannerStatus.LastUpdatedAt) _, err = time.Parse(time.RFC3339, response.BannerStatus.LastUpdatedAt) s.NoError(err) // Query for banner with 'PROVISIONING' status & no message query = bannerStatusQuery("3396a52c-6a22-4049-9593-5a63b596a104") var response2 struct{ BannerStatus *model.BannerStatus } err = ResolverClient.Post(query, &response2) s.NoError(err) s.NotNil(response2.BannerStatus) s.Equal(string(edgedb.InfraStatusProvisioning), response2.BannerStatus.Reason) s.Equal(services.ProvisioningStatusMessage, response2.BannerStatus.Message) s.False(response2.BannerStatus.Ready) s.NotEmpty(response2.BannerStatus.LastUpdatedAt) _, err = time.Parse(time.RFC3339, response2.BannerStatus.LastUpdatedAt) s.NoError(err) } func (s *Suite) TestAssignUserToEUBanner() { integration.SkipIf(s.Framework) mutation := assignUserToBannerMutation(testUser, []string{"3396a52c-6a22-4049-9593-5a63b596a102"}) var response struct{ AssignUserToBanner *model.EdgeResponsePayload } ResolverClient.MustPost(mutation, &response) s.NotNil(response.AssignUserToBanner) s.Equal(200, response.AssignUserToBanner.StatusCode) s.Equal("Successfully assigned user acct:emerald-edge-dev@testing to banners [eu-banner]", response.AssignUserToBanner.Message) } func (s *Suite) TestAssignUserToOrgBanner() { integration.SkipIf(s.Framework) mutation := assignUserToBannerMutation(testUser, []string{"3396a52c-6a22-4049-9593-5a63b596a100"}) var response struct{ AssignUserToBanner *model.EdgeResponsePayload } ResolverClient.MustPost(mutation, &response) s.NotNil(response.AssignUserToBanner) s.Equal(200, response.AssignUserToBanner.StatusCode) s.Equal("Successfully assigned user acct:emerald-edge-dev@testing to banners [test-org]", response.AssignUserToBanner.Message) } func (s *Suite) TestAssignUserToEUAndOrgBanner() { integration.SkipIf(s.Framework) mutation := assignUserToBannerMutation(testUser, []string{"3396a52c-6a22-4049-9593-5a63b596a102", "3396a52c-6a22-4049-9593-5a63b596a100"}) var response struct{ AssignUserToBanner *model.EdgeResponsePayload } ResolverClient.MustPost(mutation, &response) s.NotNil(response.AssignUserToBanner) s.Equal(200, response.AssignUserToBanner.StatusCode) s.Equal("Successfully assigned user acct:emerald-edge-dev@testing to banners [eu-banner test-org]", response.AssignUserToBanner.Message) } func (s *Suite) TestAssignRoleToUser() { integration.SkipIf(s.Framework) mutation := assignRoleToUserMutation(testUser, adminRole) var response struct{ AssignRoleToUser *model.EdgeResponsePayload } ResolverClient.MustPost(mutation, &response) s.NotNil(response.AssignRoleToUser) s.Equal(200, response.AssignRoleToUser.StatusCode) s.Equal(fmt.Sprintf("%s role added successfully", adminRole), response.AssignRoleToUser.Message) } func (s *Suite) TestAssignRolesToUser() { // Multiple roles validRoles := []string{"EDGE_BANNER_VIEWER", "EDGE_BANNER_OPERATOR"} validRolesPlus := []string{"EDGE_BANNER_VIEWER", "EDGE_L3"} var revokeRole []string mutation1 := assignRolesToUserMutation(testUser, validRoles) var response1 struct{ AssignRolesToUser *model.EdgeResponsePayload } ResolverClient.MustPost(mutation1, &response1) s.NotNil(response1.AssignRolesToUser) s.Equal(200, response1.AssignRolesToUser.StatusCode) s.Equal(fmt.Sprintf("%s role added successfully", validRoles), response1.AssignRolesToUser.Message) // Make a call again to double check new roles can still be added mutation1 = assignRolesToUserMutation(testUser, validRolesPlus) var response1plus struct{ AssignRolesToUser *model.EdgeResponsePayload } ResolverClient.MustPost(mutation1, &response1plus) s.NotNil(response1plus.AssignRolesToUser) s.Equal(200, response1plus.AssignRolesToUser.StatusCode) s.Equal(fmt.Sprintf("%s role added successfully", validRolesPlus), response1plus.AssignRolesToUser.Message) //Revoke role mutation1Revoke := assignRolesToUserMutation(testUser, revokeRole) var response1revoke struct{ AssignRolesToUser *model.EdgeResponsePayload } ResolverClient.MustPost(mutation1Revoke, &response1revoke) s.NotNil(response1revoke.AssignRolesToUser) s.Equal(200, response1revoke.AssignRolesToUser.StatusCode) s.Equal("roles revoked successfully", response1revoke.AssignRolesToUser.Message) // Invalid roles invalidRoles := []string{"Jibberish", "EDGE_BANNER_OPERATOR"} mutation2 := assignRolesToUserMutation(testUser, invalidRoles) var response2 struct{ AssignRolesToUser *model.EdgeResponsePayload } err2 := ResolverClient.Post(mutation2, &response2) s.Error(err2) s.Contains(err2.Error(), "access denied: user cannot assign the requested role Jibberish") // Empty roles in an input slice emptyRoles := []string{"EDGE_BANNER_OPERATOR", ""} mutation3 := assignRolesToUserMutation(testUser, emptyRoles) var response3 struct{ AssignRolesToUser *model.EdgeResponsePayload } err3 := ResolverClient.Post(mutation3, &response3) s.Error(err3) s.Contains(err3.Error(), "empty role input") } func (s *Suite) TestGetBannerRemoteAccessIP() { integration.SkipIf(s.Framework) query := getRemoteAccessIPQuery("98ef1fcb-dc88-4c9f-9980-c09a04564a48") var response struct{ Banner *model.Banner } err := ResolverClient.Post(query, &response) s.NoError(err) s.NotNil(response.Banner) s.Equal("136.108.49.132", response.Banner.RemoteAccessIP) } func (s *Suite) TestUpdateBannerOptOutSecurityCompliance() { integration.SkipIf(s.Framework) var response struct{ UpdateBanner *model.EdgeResponsePayload } mutation := "mutation{updateBanner(bannerEdgeId: \"4cb5d0e5-42cd-4483-8dca-547507d2adb0\", edgeSecurityCompliance: optOut){message, statusCode}}" ResolverClient.MustPost(mutation, &response) s.NotNil(response.UpdateBanner) s.Equal(200, response.UpdateBanner.StatusCode) s.Equal("Successfully updated test-sec-compliance", response.UpdateBanner.Message) var banner struct{ Banner model.Banner } query := "query {banner(bannerEdgeId: \"4cb5d0e5-42cd-4483-8dca-547507d2adb0\") {optInEdgeSecurityCompliance}}" err = ResolverClient.Post(query, &banner) s.False(banner.Banner.OptInEdgeSecurityCompliance) s.updateBannerSecurityCompliance(testSecurityBannerID, true) } func createBannerMutation(bannerName string, isOrgBanner bool, bslID string) string { //nolint: unparam return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "createBanner", Arguments: []graphb.Argument{ graphb.ArgumentString("name", bannerName), graphb.ArgumentString("description", "bff integration testing"), graphb.ArgumentBool("isOrgBanner", isOrgBanner), graphb.ArgumentString("bslId", bslID), }, Fields: graphb.Fields("statusCode", "message"), }, }, }) } func updateBannerMutation(id, name, desc string) string { //nolint: unparam return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "updateBanner", Arguments: []graphb.Argument{ graphb.ArgumentString("displayName", name), graphb.ArgumentString("description", desc), graphb.ArgumentString("bannerEdgeId", id), }, Fields: graphb.Fields("statusCode", "message"), }, }, }) } func deleteBannerMutation(bannerEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "deleteBanner", Arguments: []graphb.Argument{ graphb.ArgumentString("bannerEdgeId", bannerEdgeID), }, Fields: graphb.Fields("statusCode", "message"), }, }, }) } func getBannerQuery(bannerEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "banner", Arguments: []graphb.Argument{ graphb.ArgumentString("bannerEdgeId", bannerEdgeID), }, Fields: []*graphb.Field{ {Name: "name"}, {Name: "bannerBSLId"}, {Name: "bannerType"}, {Name: "bslDataSynced"}, {Name: "bslEntityTypes"}, {Name: "optInEdgeSecurityCompliance"}, { Name: "bannerStatus", Fields: graphb.Fields("lastUpdatedAt", "message", "reason", "ready"), }, }, }, }, }) } func getBannersQuery() string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "banners", Fields: graphb.Fields("name", "bannerBSLId", "bannerType", "bslDataSynced", "bslEntityTypes"), }, }, }) } func assignUserToBannerMutation(username string, banners []string) string { return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "assignUserToBanner", Arguments: []graphb.Argument{ graphb.ArgumentString("username", username), graphb.ArgumentStringSlice("bannerEdgeIds", banners...), }, Fields: graphb.Fields("statusCode", "message"), }, }, }) } func assignRoleToUserMutation(username string, role string) string { return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "assignRoleToUser", Arguments: []graphb.Argument{ graphb.ArgumentString("username", username), graphb.ArgumentString("role", role), }, Fields: graphb.Fields("statusCode", "message"), }, }, }) } func assignRolesToUserMutation(username string, roles []string) string { return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "assignRolesToUser", Arguments: []graphb.Argument{ graphb.ArgumentString("username", username), graphb.ArgumentStringSlice("roles", roles...), }, Fields: graphb.Fields("statusCode", "message"), }, }, }) } func bannerStatusQuery(bannerEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "bannerStatus", Arguments: []graphb.Argument{ graphb.ArgumentString("bannerEdgeId", bannerEdgeID), }, Fields: graphb.Fields("lastUpdatedAt", "message", "reason", "ready"), }, }, }) } func setUpTestBannerAndClusterStatus(bannerID, clusterEdgeID string) error { if err := runtimeClient.Create(context.Background(), buildTestBanner(bannerID)); err != nil { return err } if err := runtimeClient.Create(context.Background(), buildTestCluster(clusterEdgeID)); err != nil { return err } return runtimeClient.Create(context.Background(), buildTestGKECluster(bannerID)) } func buildTestBanner(name string) *bannerApi.Banner { return &bannerApi.Banner{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Status: bannerApi.BannerStatus{ Conditions: []metav1.Condition{ { Type: "Ready", Status: metav1.ConditionTrue, Reason: "ProvisionSucceeded", Message: "banner reconciled successfully", }, }, }, } } func buildTestCluster(name string) *clusterApi.Cluster { return &clusterApi.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Spec: clusterApi.ClusterSpec{ Type: "gke", }, Status: clusterApi.ClusterStatus{ Conditions: []metav1.Condition{ { Type: "Ready", Status: metav1.ConditionTrue, Reason: "ClusterReadyReason", Message: "cluster reconciled successfully", }, }, }, } } func buildTestGKECluster(name string) *gkeClusterApi.GKECluster { return &gkeClusterApi.GKECluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Status: gkeClusterApi.GKEClusterStatus{ Conditions: []metav1.Condition{ { Type: "Ready", Status: metav1.ConditionTrue, Reason: "GKEClusterReadyReason", Message: "GKECluster reconciled successfully", }, }, }, } } func getRemoteAccessIPQuery(bannerEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "banner", Arguments: []graphb.Argument{ graphb.ArgumentString("bannerEdgeId", bannerEdgeID), }, Fields: graphb.Fields("remoteAccessIp"), }, }, }) }