X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=capifcore%2Finternal%2Finvokermanagement%2Finvokermanagement_test.go;h=ae79c905bf0d422dbfbc0a9a0767e87299fb1ff7;hb=8fa464fbe92fe7fa2107915accfe93cb932fb021;hp=0b90de870bf72505ba39604fca94208b65c48359;hpb=0b4c4ecb52b1c04037a65644dc8c6c29981d9736;p=nonrtric%2Fplt%2Fsme.git diff --git a/capifcore/internal/invokermanagement/invokermanagement_test.go b/capifcore/internal/invokermanagement/invokermanagement_test.go index 0b90de8..ae79c90 100644 --- a/capifcore/internal/invokermanagement/invokermanagement_test.go +++ b/capifcore/internal/invokermanagement/invokermanagement_test.go @@ -3,7 +3,8 @@ // ========================LICENSE_START================================= // O-RAN-SC // %% -// Copyright (C) 2022: Nordix Foundation +// Copyright (C) 2022-2023: Nordix Foundation +// Copyright (C) 2024: OpenInfra Foundation Europe // %% // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,17 +24,20 @@ import ( "fmt" "net/http" "os" - "path" "strings" "testing" + "time" + "oransc.org/nonrtric/capifcore/internal/eventsapi" "oransc.org/nonrtric/capifcore/internal/invokermanagementapi" + "oransc.org/nonrtric/capifcore/internal/keycloak" "github.com/labstack/echo/v4" "oransc.org/nonrtric/capifcore/internal/common29122" "oransc.org/nonrtric/capifcore/internal/publishserviceapi" + keycloackmocks "oransc.org/nonrtric/capifcore/internal/keycloak/mocks" "oransc.org/nonrtric/capifcore/internal/publishservice" publishmocks "oransc.org/nonrtric/capifcore/internal/publishservice/mocks" @@ -41,6 +45,7 @@ import ( "github.com/deepmap/oapi-codegen/pkg/testutil" echomiddleware "github.com/labstack/echo/v4/middleware" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) func TestOnboardInvoker(t *testing.T) { @@ -54,11 +59,20 @@ func TestOnboardInvoker(t *testing.T) { AefProfiles: &aefProfiles, }, } - publishRegisterMock := publishmocks.PublishRegister{} - publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices) - invokerUnderTest, requestHandler := getEcho(&publishRegisterMock) invokerInfo := "invoker a" + wantedInvokerSecret := "onboarding_secret_" + strings.Replace(invokerInfo, " ", "_", 1) + var client keycloak.Client + client.Secret = &wantedInvokerSecret + publishRegisterMock := publishmocks.PublishRegister{} + publishRegisterMock.On("GetAllowedPublishedServices", mock.AnythingOfType("[]publishserviceapi.ServiceAPIDescription")).Return(publishedServices) + + accessMgmMock := keycloackmocks.AccessManagement{} + accessMgmMock.On("AddClient", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil) + accessMgmMock.On("GetClientRepresentation", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&client, nil) + + invokerUnderTest, eventChannel, requestHandler := getEcho(&publishRegisterMock, &accessMgmMock) + newInvoker := getInvoker(invokerInfo) // Onboard a valid invoker @@ -72,34 +86,50 @@ func TestOnboardInvoker(t *testing.T) { assert.Equal(t, wantedInvokerId, *resultInvoker.ApiInvokerId) assert.Equal(t, newInvoker.NotificationDestination, resultInvoker.NotificationDestination) assert.Equal(t, newInvoker.OnboardingInformation.ApiInvokerPublicKey, resultInvoker.OnboardingInformation.ApiInvokerPublicKey) - wantedInvokerSecret := "onboarding_secret_" + strings.Replace(invokerInfo, " ", "_", 1) + assert.Equal(t, wantedInvokerSecret, *resultInvoker.OnboardingInformation.OnboardingSecret) assert.Equal(t, "http://example.com/onboardedInvokers/"+*resultInvoker.ApiInvokerId, result.Recorder.Header().Get(echo.HeaderLocation)) assert.True(t, invokerUnderTest.IsInvokerRegistered(wantedInvokerId)) assert.True(t, invokerUnderTest.VerifyInvokerSecret(wantedInvokerId, wantedInvokerSecret)) - publishRegisterMock.AssertCalled(t, "GetAllPublishedServices") + + publishRegisterMock.AssertCalled(t, "GetAllowedPublishedServices", mock.AnythingOfType("[]publishserviceapi.ServiceAPIDescription")) + assert.Equal(t, invokermanagementapi.APIList(publishedServices), *resultInvoker.ApiList) + if invokerEvent, timeout := waitForEvent(eventChannel, 1*time.Second); timeout { + assert.Fail(t, "No event sent") + } else { + assert.Equal(t, *resultInvoker.ApiInvokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0]) + assert.Equal(t, eventsapi.CAPIFEventAPIINVOKERONBOARDED, invokerEvent.Events) + } + + // Onboarding the same invoker should result in Forbidden + result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler) + + assert.Equal(t, http.StatusForbidden, result.Code()) + var problemDetails common29122.ProblemDetails + err = result.UnmarshalBodyToObject(&problemDetails) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, http.StatusForbidden, *problemDetails.Status) + assert.Contains(t, *problemDetails.Cause, "already onboarded") // Onboard an invoker missing required NotificationDestination, should get 400 with problem details invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{ OnboardingInformation: invokermanagementapi.OnboardingInformation{ - ApiInvokerPublicKey: "key", + ApiInvokerPublicKey: "newKey", }, } result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler) assert.Equal(t, http.StatusBadRequest, result.Code()) - var problemDetails common29122.ProblemDetails err = result.UnmarshalBodyToObject(&problemDetails) assert.NoError(t, err, "error unmarshaling response") - badRequest := http.StatusBadRequest - assert.Equal(t, &badRequest, problemDetails.Status) + assert.Equal(t, http.StatusBadRequest, *problemDetails.Status) assert.Contains(t, *problemDetails.Cause, "missing") assert.Contains(t, *problemDetails.Cause, "NotificationDestination") // Onboard an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details invalidInvoker = invokermanagementapi.APIInvokerEnrolmentDetails{ - NotificationDestination: "url", + NotificationDestination: "http://golang.cafe/", } result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler) @@ -107,67 +137,88 @@ func TestOnboardInvoker(t *testing.T) { assert.Equal(t, http.StatusBadRequest, result.Code()) err = result.UnmarshalBodyToObject(&problemDetails) assert.NoError(t, err, "error unmarshaling response") - assert.Equal(t, &badRequest, problemDetails.Status) + assert.Equal(t, http.StatusBadRequest, *problemDetails.Status) assert.Contains(t, *problemDetails.Cause, "missing") assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey") } func TestDeleteInvoker(t *testing.T) { - publishRegisterMock := publishmocks.PublishRegister{} - publishRegisterMock.On("GetAllPublishedServices").Return([]publishserviceapi.ServiceAPIDescription{}) - invokerUnderTest, requestHandler := getEcho(&publishRegisterMock) + invokerUnderTest, eventChannel, requestHandler := getEcho(nil, nil) + invokerId := "invokerId" newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{ + ApiInvokerId: &invokerId, NotificationDestination: "url", OnboardingInformation: invokermanagementapi.OnboardingInformation{ ApiInvokerPublicKey: "key", }, } - - // Onboard an invoker - result := testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler) - - invokerUrl := result.Recorder.Header().Get(echo.HeaderLocation) - assert.True(t, invokerUnderTest.IsInvokerRegistered(path.Base(invokerUrl))) + invokerUnderTest.onboardedInvokers[invokerId] = newInvoker + assert.True(t, invokerUnderTest.IsInvokerRegistered(invokerId)) // Delete the invoker - result = testutil.NewRequest().Delete(invokerUrl).Go(t, requestHandler) + result := testutil.NewRequest().Delete("/onboardedInvokers/"+invokerId).Go(t, requestHandler) assert.Equal(t, http.StatusNoContent, result.Code()) - assert.False(t, invokerUnderTest.IsInvokerRegistered(path.Base(invokerUrl))) + assert.False(t, invokerUnderTest.IsInvokerRegistered(invokerId)) + if invokerEvent, timeout := waitForEvent(eventChannel, 1*time.Second); timeout { + assert.Fail(t, "No event sent") + } else { + assert.Equal(t, invokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0]) + assert.Equal(t, eventsapi.CAPIFEventAPIINVOKEROFFBOARDED, invokerEvent.Events) + } +} + +func TestFailedUpdateInvoker(t *testing.T) { + publishRegisterMock := publishmocks.PublishRegister{} + publishRegisterMock.On("GetAllPublishedServices").Return([]publishserviceapi.ServiceAPIDescription{}) + serviceUnderTest, _, requestHandler := getEcho(&publishRegisterMock, nil) + + invokerInfo := "invoker a" + invokerId := "api_invoker_id_" + strings.Replace(invokerInfo, " ", "_", 1) + + // Attempt to update with an invoker without the ApiInvokerId provided in the parameter body. We should get 400 with problem details. + invalidInvoker := getInvoker(invokerInfo) + serviceUnderTest.onboardedInvokers[invokerId] = invalidInvoker + + result := testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler) + assert.Equal(t, http.StatusBadRequest, result.Code()) + + var problemDetails common29122.ProblemDetails + err := result.UnmarshalBodyToObject(&problemDetails) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, http.StatusBadRequest, *problemDetails.Status) + + assert.Contains(t, *problemDetails.Cause, "APIInvokerEnrolmentDetails ApiInvokerId doesn't match path parameter") } func TestUpdateInvoker(t *testing.T) { publishRegisterMock := publishmocks.PublishRegister{} publishRegisterMock.On("GetAllPublishedServices").Return([]publishserviceapi.ServiceAPIDescription{}) - _, requestHandler := getEcho(&publishRegisterMock) + serviceUnderTest, _, requestHandler := getEcho(&publishRegisterMock, nil) - newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{ - NotificationDestination: "url", + invokerId := "invokerId" + invoker := invokermanagementapi.APIInvokerEnrolmentDetails{ + ApiInvokerId: &invokerId, + NotificationDestination: "http://golang.cafe/", OnboardingInformation: invokermanagementapi.OnboardingInformation{ ApiInvokerPublicKey: "key", }, } - - // Onboard an invoker - result := testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler) - var resultInvoker invokermanagementapi.APIInvokerEnrolmentDetails - result.UnmarshalBodyToObject(&resultInvoker) + serviceUnderTest.onboardedInvokers[invokerId] = invoker // Update the invoker with valid invoker, should return 200 with updated invoker details - invokerId := resultInvoker.ApiInvokerId - invokerUrl := result.Recorder.Header().Get(echo.HeaderLocation) - resultInvoker.ApiList = nil - newNotifURL := "newUrl" - resultInvoker.NotificationDestination = common29122.Uri(newNotifURL) + newNotifURL := "http://golang.org/" + invoker.NotificationDestination = common29122.Uri(newNotifURL) newPublicKey := "newPublicKey" - resultInvoker.OnboardingInformation.ApiInvokerPublicKey = newPublicKey - result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(resultInvoker).Go(t, requestHandler) + invoker.OnboardingInformation.ApiInvokerPublicKey = newPublicKey + result := testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invoker).Go(t, requestHandler) + var resultInvoker invokermanagementapi.APIInvokerEnrolmentDetails assert.Equal(t, http.StatusOK, result.Code()) err := result.UnmarshalBodyToObject(&resultInvoker) assert.NoError(t, err, "error unmarshaling response") - assert.Equal(t, invokerId, resultInvoker.ApiInvokerId) + assert.Equal(t, invokerId, *resultInvoker.ApiInvokerId) assert.Equal(t, newNotifURL, string(resultInvoker.NotificationDestination)) assert.Equal(t, newPublicKey, resultInvoker.OnboardingInformation.ApiInvokerPublicKey) @@ -176,29 +227,28 @@ func TestUpdateInvoker(t *testing.T) { ApiInvokerPublicKey: "key", } invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{ - ApiInvokerId: invokerId, + ApiInvokerId: &invokerId, OnboardingInformation: validOnboardingInfo, } - result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(invalidInvoker).Go(t, requestHandler) + result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler) assert.Equal(t, http.StatusBadRequest, result.Code()) var problemDetails common29122.ProblemDetails err = result.UnmarshalBodyToObject(&problemDetails) assert.NoError(t, err, "error unmarshaling response") - badRequest := http.StatusBadRequest - assert.Equal(t, &badRequest, problemDetails.Status) + assert.Equal(t, http.StatusBadRequest, *problemDetails.Status) assert.Contains(t, *problemDetails.Cause, "missing") assert.Contains(t, *problemDetails.Cause, "NotificationDestination") // Update with an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details - invalidInvoker.NotificationDestination = "url" + invalidInvoker.NotificationDestination = "http://golang.org/" invalidInvoker.OnboardingInformation = invokermanagementapi.OnboardingInformation{} - result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(invalidInvoker).Go(t, requestHandler) + result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler) assert.Equal(t, http.StatusBadRequest, result.Code()) err = result.UnmarshalBodyToObject(&problemDetails) assert.NoError(t, err, "error unmarshaling response") - assert.Equal(t, &badRequest, problemDetails.Status) + assert.Equal(t, http.StatusBadRequest, *problemDetails.Status) assert.Contains(t, *problemDetails.Cause, "missing") assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey") @@ -206,25 +256,23 @@ func TestUpdateInvoker(t *testing.T) { invalidId := "1" invalidInvoker.ApiInvokerId = &invalidId invalidInvoker.OnboardingInformation = validOnboardingInfo - result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(invalidInvoker).Go(t, requestHandler) + result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler) assert.Equal(t, http.StatusBadRequest, result.Code()) err = result.UnmarshalBodyToObject(&problemDetails) assert.NoError(t, err, "error unmarshaling response") - assert.Equal(t, &badRequest, problemDetails.Status) - assert.Contains(t, *problemDetails.Cause, "not matching") - assert.Contains(t, *problemDetails.Cause, "ApiInvokerId") + assert.Equal(t, http.StatusBadRequest, *problemDetails.Status) + assert.Contains(t, *problemDetails.Cause, "APIInvokerEnrolmentDetails ApiInvokerId doesn't match path parameter") - // Update an invoker that has not been onboarded, shold get 404 with problem details + // Update an invoker that has not been onboarded, should get 404 with problem details missingId := "1" - newInvoker.ApiInvokerId = &missingId - result = testutil.NewRequest().Put("/onboardedInvokers/"+missingId).WithJsonBody(newInvoker).Go(t, requestHandler) + invoker.ApiInvokerId = &missingId + result = testutil.NewRequest().Put("/onboardedInvokers/"+missingId).WithJsonBody(invoker).Go(t, requestHandler) assert.Equal(t, http.StatusNotFound, result.Code()) err = result.UnmarshalBodyToObject(&problemDetails) assert.NoError(t, err, "error unmarshaling response") - notFound := http.StatusNotFound - assert.Equal(t, ¬Found, problemDetails.Status) + assert.Equal(t, http.StatusNotFound, *problemDetails.Status) assert.Contains(t, *problemDetails.Cause, "not been onboarded") assert.Contains(t, *problemDetails.Cause, "invoker") } @@ -250,20 +298,26 @@ func TestGetInvokerApiList(t *testing.T) { }) publishRegisterMock := publishmocks.PublishRegister{} publishRegisterMock.On("GetAllPublishedServices").Return(apiList) - invokerUnderTest, requestHandler := getEcho(&publishRegisterMock) + invokerUnderTest, _, _ := getEcho(&publishRegisterMock, nil) invokerInfo := "invoker a" newInvoker := getInvoker(invokerInfo) - testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler) - newInvoker = getInvoker("invoker b") - testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler) - - wantedApiList := invokerUnderTest.GetInvokerApiList("api_invoker_id_" + strings.Replace(invokerInfo, " ", "_", 1)) + invokerAId := "api_invoker_id_" + strings.ReplaceAll(invokerInfo, " ", "_") + newInvoker.ApiInvokerId = &invokerAId + invokerUnderTest.onboardedInvokers[invokerAId] = newInvoker + invokerInfo = "invoker b" + newInvoker = getInvoker(invokerInfo) + invokerId := "api_invoker_id_" + strings.ReplaceAll(invokerInfo, " ", "_") + newInvoker.ApiInvokerId = &invokerId + invokerUnderTest.onboardedInvokers[invokerId] = newInvoker + + wantedApiList := invokerUnderTest.GetInvokerApiList(invokerAId) assert.NotNil(t, wantedApiList) + assert.Len(t, *wantedApiList, 2) assert.Equal(t, apiId, *(*wantedApiList)[0].ApiId) } -func getEcho(publishRegister publishservice.PublishRegister) (*InvokerManager, *echo.Echo) { +func getEcho(publishRegister publishservice.PublishRegister, keycloakMgm keycloak.AccessManagement) (*InvokerManager, chan eventsapi.EventNotification, *echo.Echo) { swagger, err := invokermanagementapi.GetSwagger() if err != nil { fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) @@ -272,14 +326,15 @@ func getEcho(publishRegister publishservice.PublishRegister) (*InvokerManager, * swagger.Servers = nil - im := NewInvokerManager(publishRegister) + eventChannel := make(chan eventsapi.EventNotification) + im := NewInvokerManager(publishRegister, keycloakMgm, eventChannel) e := echo.New() e.Use(echomiddleware.Logger()) e.Use(middleware.OapiRequestValidator(swagger)) invokermanagementapi.RegisterHandlers(e, im) - return im, e + return im, eventChannel, e } func getAefProfile(aefId string) publishserviceapi.AefProfile { @@ -300,7 +355,7 @@ func getAefProfile(aefId string) publishserviceapi.AefProfile { func getInvoker(invokerInfo string) invokermanagementapi.APIInvokerEnrolmentDetails { newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{ ApiInvokerInformation: &invokerInfo, - NotificationDestination: "url", + NotificationDestination: "http://golang.cafe/", OnboardingInformation: invokermanagementapi.OnboardingInformation{ ApiInvokerPublicKey: "key", }, @@ -308,3 +363,14 @@ func getInvoker(invokerInfo string) invokermanagementapi.APIInvokerEnrolmentDeta } return newInvoker } + +// waitForEvent waits for the channel to receive an event for the specified max timeout. +// Returns true if waiting timed out. +func waitForEvent(ch chan eventsapi.EventNotification, timeout time.Duration) (*eventsapi.EventNotification, bool) { + select { + case event := <-ch: + return &event, false // completed normally + case <-time.After(timeout): + return nil, true // timed out + } +}