diff --git a/internal/service-manager/client.go b/internal/service-manager/client.go index 14d9a5ac4..ad3f38e16 100644 --- a/internal/service-manager/client.go +++ b/internal/service-manager/client.go @@ -131,6 +131,10 @@ func (c *Client) SetHTTPClient(httpClient *http.Client) { c.httpClient = httpClient } +func (c *Client) SetSMURL(smURL string) { + c.smURL = smURL +} + func (c *Client) ServiceOfferings() (*types.ServiceOfferings, error) { req, err := http.NewRequest(http.MethodGet, c.smURL+ServiceOfferingsPath, nil) if err != nil { diff --git a/internal/service-manager/client_test.go b/internal/service-manager/client_test.go new file mode 100644 index 000000000..493f986ee --- /dev/null +++ b/internal/service-manager/client_test.go @@ -0,0 +1,170 @@ +package servicemanager_test + +import ( + "context" + "encoding/json" + "fmt" + "log" + "log/slog" + "net/http" + "net/http/httptest" + "os" + "testing" + + servicemanager "github.com/kyma-project/btp-manager/internal/service-manager" + "github.com/kyma-project/btp-manager/internal/service-manager/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + serviceOfferingsJSONPath = "testdata/service_offerings.json" +) + +func TestClient(t *testing.T) { + // given + secretProvider := newFakeSecretProvider() + srv, err := initFakeServer() + require.NoError(t, err) + + srv.Start() + defer srv.Close() + httpClient := srv.Client() + url := srv.URL + + t.Run("should get service offerings available for the default credentials", func(t *testing.T) { + // given + ctx := context.TODO() + secretProvider.AddSecret(defaultSecret()) + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + + var expectedServiceOfferings types.ServiceOfferings + soJSON, err := getResourcesFromJSONFile(serviceOfferingsJSONPath) + require.NoError(t, err) + + soBytes, err := json.Marshal(soJSON) + require.NoError(t, err) + + err = json.Unmarshal(soBytes, &expectedServiceOfferings) + require.NoError(t, err) + + // when + err = smClient.Defaults(ctx) + + // then + require.NoError(t, err) + + // given + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + + // when + so, err := smClient.ServiceOfferings() + + // then + require.NoError(t, err) + assert.Len(t, so.ServiceOfferings, 4) + assert.ElementsMatch(t, expectedServiceOfferings.ServiceOfferings, so.ServiceOfferings) + }) +} + +func initFakeServer() (*httptest.Server, error) { + smHandler, err := newFakeSMHandler() + if err != nil { + return nil, fmt.Errorf("while creating new fake SM handler: %w", err) + } + + mux := http.NewServeMux() + mux.HandleFunc("GET /v1/service_offerings", smHandler.getServiceOfferings) + + srv := httptest.NewUnstartedServer(mux) + + return srv, nil +} + +type fakeSMHandler struct { + serviceOfferings map[string]interface{} +} + +func newFakeSMHandler() (*fakeSMHandler, error) { + so, err := getResourcesFromJSONFile(serviceOfferingsJSONPath) + if err != nil { + return nil, fmt.Errorf("while getting service offerings from JSON file: %w", err) + } + + return &fakeSMHandler{serviceOfferings: so}, nil +} + +func getResourcesFromJSONFile(jsonFilePath string) (map[string]interface{}, error) { + var buf map[string]interface{} + f, err := os.Open(jsonFilePath) + defer f.Close() + if err != nil { + return nil, fmt.Errorf("while reading resources from JSON file: %w", err) + } + + d := json.NewDecoder(f) + if err := d.Decode(&buf); err != nil { + return nil, fmt.Errorf("while decoding resources JSON: %w", err) + } + return buf, nil +} + +func (h *fakeSMHandler) getServiceOfferings(w http.ResponseWriter, r *http.Request) { + data, err := json.Marshal(h.serviceOfferings) + if err != nil { + log.Println("error while marshalling service offerings data: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + if _, err = w.Write(data); err != nil { + w.WriteHeader(http.StatusBadRequest) + log.Println("error while writing service offerings data: %w", err) + return + } +} + +type fakeSecretProvider struct { + secrets []*corev1.Secret +} + +func newFakeSecretProvider() *fakeSecretProvider { + return &fakeSecretProvider{secrets: make([]*corev1.Secret, 0)} +} + +func (p *fakeSecretProvider) AddSecret(secret *corev1.Secret) { + p.secrets = append(p.secrets, secret) +} + +func (p *fakeSecretProvider) GetByNameAndNamespace(ctx context.Context, name, namespace string) (*corev1.Secret, error) { + for _, secret := range p.secrets { + if secret.Name == name && secret.Namespace == namespace { + return secret, nil + } + } + return nil, fmt.Errorf("secret not found") +} + +func (p *fakeSecretProvider) clean() { + p.secrets = make([]*corev1.Secret, 0) +} + +func defaultSecret() *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sap-btp-service-operator", + Namespace: "kyma-system", + }, + StringData: map[string]string{ + "clientid": "default-client-id", + "clientsecret": "default-client-secret", + "sm_url": "https://default-sm-url.local", + "tokenurl": "https://default-token-url.local", + "tokenurlsuffix": "/oauth/token", + }, + } +} diff --git a/internal/service-manager/testdata/service_offerings.json b/internal/service-manager/testdata/service_offerings.json new file mode 100644 index 000000000..44becfe21 --- /dev/null +++ b/internal/service-manager/testdata/service_offerings.json @@ -0,0 +1,111 @@ +{ + "num_items": 4, + "items": [ + { + "id": "fc26622b-aeb2-4f3c-95da-8eb337a26883", + "ready": true, + "name": "service1", + "description": "Service 1 description", + "bindable": true, + "instances_retrievable": false, + "bindings_retrievable": false, + "plan_updateable": false, + "allow_context_updates": false, + "metadata": { + "createBindingDocumentationUrl": "https://service1-test.local/create-binding.html", + "discoveryCenterUrl": "https://discovery-center.local/serviceCatalog/service1", + "displayName": "Service 1", + "documentationUrl": "https://service1-test.local/documentation.html", + "imageUrl": "data:image/svg+xml;base64,c2VydmljZTEK", + "longDescription": "Service 1 long description", + "serviceInventoryId": "SERVICE-1", + "shareable": true, + "supportUrl": "https://service1-test.local/support.html" + }, + "broker_id": "9352e325-d62b-4cc9-bf73-b2db77a343aa", + "catalog_id": "a5b7679a-36e6-4c85-8f4f-ab7a1ab1ac16", + "catalog_name": "service1", + "created_at": "2024-01-11T01:01:00", + "updated_at": "2024-01-12T02:02:00" + }, + { + "id": "1d87e6e3-12b4-4a04-82bb-ddf7299d52c9", + "ready": true, + "name": "service2", + "description": "Service 2 description", + "bindable": true, + "instances_retrievable": false, + "bindings_retrievable": false, + "plan_updateable": true, + "allow_context_updates": false, + "tags": [ + "service", + "two" + ], + "metadata": { + "shareable": false, + "displayName": "Service 2" + }, + "broker_id": "c208c628-4600-4bfc-b92c-dc1e2bd8632b", + "catalog_id": "fb4035a9-75ec-4049-9b5f-b9f19af48e9a", + "catalog_name": "service2", + "created_at": "2024-02-11T02:02:00", + "updated_at": "2024-02-12T03:03:00" + }, + { + "id": "7df0a7a5-3bda-4af4-9fdf-763ad4c01bb7", + "ready": true, + "name": "service3", + "description": "Service 3 description", + "bindable": true, + "instances_retrievable": true, + "bindings_retrievable": false, + "plan_updateable": false, + "allow_context_updates": false, + "tags": [ + "service-three" + ], + "metadata": { + "documentationUrl": "https://service3-test.local/documentation.html", + "serviceInventoryId": "SERVICE-3", + "displayName": "Service 3", + "imageUrl": "data:image/png;base64,c2VydmljZTMK" + }, + "broker_id": "f5ccc8b2-9723-4ae9-834a-46c00d177df7", + "catalog_id": "service3-service-broker", + "catalog_name": "service3", + "created_at": "2024-03-11T03:03:00", + "updated_at": "2024-03-12T04:04:00" + }, + { + "id": "69e313ac-6633-4fa9-a9d5-8aaf569bc966", + "ready": true, + "name": "service4", + "description": "Service 4 description", + "bindable": true, + "instances_retrievable": false, + "bindings_retrievable": false, + "plan_updateable": false, + "allow_context_updates": false, + "tags": [ + "service", + "four", + "foursvc" + ], + "metadata": { + "longDescription": "Service 4 long description", + "documentationUrl": "https://service4-test.local/documentation.html", + "providerDisplayName": "GREAT COMPANY", + "serviceInventoryId": "SERVICE-4", + "displayName": "Service 4", + "imageUrl": "data:image/svg+xml;base64,c2VydmljZTQK", + "supportUrl": "https://service4-test.local/support.html" + }, + "broker_id": "c94651ef-9997-485c-aa65-5ef5ed64e619", + "catalog_id": "1277f478-9670-4d62-a068-039329ff086b", + "catalog_name": "service4", + "created_at": "2024-04-11T04:04:00", + "updated_at": "2024-04-12T05:05:00" + } + ] +} \ No newline at end of file