From d1630efa37119c854d00f68c70f72610ae998f20 Mon Sep 17 00:00:00 2001 From: Ruben Aleman Date: Mon, 12 Jan 2026 11:24:36 +0100 Subject: [PATCH] feat(edge): add waiter for edge service --- CHANGELOG.md | 7 +- services/edge/CHANGELOG.md | 3 + services/edge/VERSION | 2 +- services/edge/wait/wait.go | 258 +++++++++++++++++++ services/edge/wait/wait_test.go | 425 ++++++++++++++++++++++++++++++++ 5 files changed, 692 insertions(+), 3 deletions(-) create mode 100644 services/edge/wait/wait.go create mode 100644 services/edge/wait/wait_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a79d28e..6e9147253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## Release (2026-XX-YY) -- `edge`: [v0.1.0](services/edge/CHANGELOG.md#v010) - - **New:** STACKIT Edge Cloud service +- `edge`: + - [v0.2.0](services/edge/CHANGELOG.md#v020) + - **Feature:** Add waiter methods for the API + - [v0.1.0](services/edge/CHANGELOG.md#v010) + - **New:** STACKIT Edge Cloud service - `alb`: [v0.8.0](services/alb/CHANGELOG.md#v080) - **Feature:** Switch from `v2beta` API version to `v2` version. - **Feature:** `MaxCredentials` field added to `GetQuotaResponse` diff --git a/services/edge/CHANGELOG.md b/services/edge/CHANGELOG.md index 565e0f96b..3e444f3a5 100644 --- a/services/edge/CHANGELOG.md +++ b/services/edge/CHANGELOG.md @@ -1,2 +1,5 @@ +## v0.2.0 +- **Feature:** Add waiter methods for the API + ## v0.1.0 - **New:** STACKIT Edge Cloud service diff --git a/services/edge/VERSION b/services/edge/VERSION index b82608c0b..1474d00f0 100644 --- a/services/edge/VERSION +++ b/services/edge/VERSION @@ -1 +1 @@ -v0.1.0 +v0.2.0 diff --git a/services/edge/wait/wait.go b/services/edge/wait/wait.go new file mode 100644 index 000000000..a16a3cd9d --- /dev/null +++ b/services/edge/wait/wait.go @@ -0,0 +1,258 @@ +package wait + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/wait" + "github.com/stackitcloud/stackit-sdk-go/services/edge" +) + +const timeoutMinutes time.Duration = 10 + +var ( + ErrInstanceNotFound = errors.New("instance not found") + ErrInstanceStatusUndefined = errors.New("instance status undefined") + ErrInstanceCreationFailed = errors.New("instance creation failed") + ErrInstanceIsBeingDeleted = errors.New("instance is being deleted") +) + +// EdgeCloudApiClient is the interface for Edge Cloud API calls which require a waiter. +type EdgeCloudApiClient interface { + GetInstanceExecute(ctx context.Context, projectId, regionId, instanceId string) (*edge.Instance, error) + GetInstanceByNameExecute(ctx context.Context, projectId, regionId, displayName string) (*edge.Instance, error) + GetTokenByInstanceId(ctx context.Context, projectId string, regionId string, instanceId string) edge.ApiGetTokenByInstanceIdRequest + GetTokenByInstanceName(ctx context.Context, projectId string, regionId string, displayName string) edge.ApiGetTokenByInstanceNameRequest + GetKubeconfigByInstanceId(ctx context.Context, projectId string, regionId string, instanceId string) edge.ApiGetKubeconfigByInstanceIdRequest + GetKubeconfigByInstanceName(ctx context.Context, projectId string, regionId string, displayName string) edge.ApiGetKubeconfigByInstanceNameRequest +} + +// createOrUpdateInstanceWaitHandler contains the shared logic for waiting on instance creation or updates. +func createOrUpdateInstanceWaitHandler(ctx context.Context, getInstance func(ctx context.Context) (*edge.Instance, error)) *wait.AsyncActionHandler[edge.Instance] { + handler := wait.New(func() (waitFinished bool, response *edge.Instance, err error) { + instance, err := getInstance(ctx) + if err != nil { + return false, nil, err + } + + if instance == nil || instance.Status == nil { + return false, nil, ErrInstanceNotFound + } + if instance == nil || instance.Status == nil { + return false, nil, ErrInstanceStatusUndefined + } + + status := *instance.Status + switch status { + case edge.INSTANCESTATUS_ACTIVE: + return true, instance, nil + case edge.INSTANCESTATUS_ERROR: + return true, instance, ErrInstanceCreationFailed + case edge.INSTANCESTATUS_RECONCILING: + return false, nil, nil + case edge.INSTANCESTATUS_DELETING: + return true, instance, ErrInstanceIsBeingDeleted + default: + return false, nil, nil + } + }) + handler.SetTimeout(timeoutMinutes * time.Minute) + return handler +} + +// CreateOrUpdateInstanceWaitHandler waits for instance creation or update by ID to complete. +func CreateOrUpdateInstanceWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, instanceId string) *wait.AsyncActionHandler[edge.Instance] { + return createOrUpdateInstanceWaitHandler(ctx, func(ctx context.Context) (*edge.Instance, error) { + return a.GetInstanceExecute(ctx, projectId, regionId, instanceId) + }) +} + +// CreateOrUpdateInstanceByNameWaitHandler waits for instance creation or update by name to complete. +func CreateOrUpdateInstanceByNameWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, displayName string) *wait.AsyncActionHandler[edge.Instance] { + return createOrUpdateInstanceWaitHandler(ctx, func(ctx context.Context) (*edge.Instance, error) { + return a.GetInstanceByNameExecute(ctx, projectId, regionId, displayName) + }) +} + +// deleteInstanceWaitHandler contains the shared logic for waiting on instance deletion. +func deleteInstanceWaitHandler(ctx context.Context, getInstance func(ctx context.Context) (*edge.Instance, error)) *wait.AsyncActionHandler[edge.Instance] { + handler := wait.New(func() (waitFinished bool, response *edge.Instance, err error) { + _, err = getInstance(ctx) + if err == nil { + return false, nil, nil + } + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { + return true, nil, nil + } + return false, nil, err + }) + handler.SetTimeout(timeoutMinutes * time.Minute) + return handler +} + +// DeleteInstanceWaitHandler waits for instance deletion by ID. +func DeleteInstanceWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, instanceId string) *wait.AsyncActionHandler[edge.Instance] { + return deleteInstanceWaitHandler(ctx, func(ctx context.Context) (*edge.Instance, error) { + return a.GetInstanceExecute(ctx, projectId, regionId, instanceId) + }) +} + +// DeleteInstanceByNameWaitHandler waits for instance deletion by name. +func DeleteInstanceByNameWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, displayName string) *wait.AsyncActionHandler[edge.Instance] { + return deleteInstanceWaitHandler(ctx, func(ctx context.Context) (*edge.Instance, error) { + return a.GetInstanceByNameExecute(ctx, projectId, regionId, displayName) + }) +} + +// kubeconfigWaitHandlerHelper contains the shared logic for waiting for the instance to become ready before retrieving the kubeconfig. +func kubeconfigWaitHandlerHelper(ctx context.Context, checkInstance func(ctx context.Context) error, getKubeconfig func(ctx context.Context) (*edge.Kubeconfig, error)) *wait.AsyncActionHandler[edge.Kubeconfig] { + handler := wait.New(func() (waitFinished bool, response *edge.Kubeconfig, err error) { + err = checkInstance(ctx) + if err != nil { + return false, nil, err + } + kubeconfig, err := getKubeconfig(ctx) + var oapiErr *oapierror.GenericOpenAPIError + if err != nil { + if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { + return false, nil, nil + } + return false, nil, err + } + return true, kubeconfig, nil + }) + handler.SetTimeout(timeoutMinutes * time.Minute) + return handler +} + +// KubeconfigWaitHandler waits the instance to become ready before retrieving the kubeconfig by instance ID. +func KubeconfigWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, instanceId string, expirationSeconds *int64) *wait.AsyncActionHandler[edge.Kubeconfig] { + return kubeconfigWaitHandlerHelper(ctx, + func(ctx context.Context) error { + return checkInstanceExistsWithUsableStatus(ctx, a, projectId, regionId, instanceId) + }, + func(ctx context.Context) (*edge.Kubeconfig, error) { + req := a.GetKubeconfigByInstanceId(ctx, projectId, regionId, instanceId) + if expirationSeconds != nil { + req = req.ExpirationSeconds(*expirationSeconds) + } + return req.Execute() + }, + ) +} + +// KubeconfigByInstanceNameWaitHandler waits the instance to become ready before retrieving the kubeconfig by instance displayName. +func KubeconfigByInstanceNameWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, displayName string, expirationSeconds *int64) *wait.AsyncActionHandler[edge.Kubeconfig] { + return kubeconfigWaitHandlerHelper(ctx, + func(ctx context.Context) error { + return checkInstanceNameExistsWithUsableStatus(ctx, a, projectId, regionId, displayName) + }, + func(ctx context.Context) (*edge.Kubeconfig, error) { + req := a.GetKubeconfigByInstanceName(ctx, projectId, regionId, displayName) + if expirationSeconds != nil { + req = req.ExpirationSeconds(*expirationSeconds) + } + return req.Execute() + }, + ) +} + +// tokenWaitHandlerHelper contains the shared logic for waiting for the instance to become ready before retrieving the service token. +func tokenWaitHandlerHelper(ctx context.Context, checkInstance func(ctx context.Context) error, getToken func(ctx context.Context) (*edge.Token, error)) *wait.AsyncActionHandler[edge.Token] { + handler := wait.New(func() (waitFinished bool, response *edge.Token, err error) { + err = checkInstance(ctx) + if err != nil { + return false, nil, err + } + token, err := getToken(ctx) + var oapiErr *oapierror.GenericOpenAPIError + if err != nil { + if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { + return false, nil, nil + } + return false, nil, err + } + return true, token, nil + }) + handler.SetTimeout(timeoutMinutes * time.Minute) + return handler +} + +// TokenWaitHandler waits for the instance to become ready before retrieving the service token by instance ID. +func TokenWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, instanceId string, expirationSeconds *int64) *wait.AsyncActionHandler[edge.Token] { + return tokenWaitHandlerHelper(ctx, + func(ctx context.Context) error { + return checkInstanceExistsWithUsableStatus(ctx, a, projectId, regionId, instanceId) + }, + func(ctx context.Context) (*edge.Token, error) { + req := a.GetTokenByInstanceId(ctx, projectId, regionId, instanceId) + if expirationSeconds != nil { + req = req.ExpirationSeconds(*expirationSeconds) + } + return req.Execute() + }, + ) +} + +// TokenByInstanceNameWaitHandler waits for the instance to become ready before retrieving the service token by instance displayName. +func TokenByInstanceNameWaitHandler(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, displayName string, expirationSeconds *int64) *wait.AsyncActionHandler[edge.Token] { + return tokenWaitHandlerHelper(ctx, + func(ctx context.Context) error { + return checkInstanceNameExistsWithUsableStatus(ctx, a, projectId, regionId, displayName) + }, + func(ctx context.Context) (*edge.Token, error) { + req := a.GetTokenByInstanceName(ctx, projectId, regionId, displayName) + if expirationSeconds != nil { + req = req.ExpirationSeconds(*expirationSeconds) + } + return req.Execute() + }, + ) +} + +// checkInstanceUsableStatus contains the shared logic for checking instance status. +func checkInstanceUsableStatus(ctx context.Context, getInstance func(ctx context.Context) (*edge.Instance, error), identifierType, identifierValue string) error { + instance, err := getInstance(ctx) + if err != nil { + return err + } + if instance == nil { + return ErrInstanceNotFound + } + if instance.Status == nil { + return ErrInstanceStatusUndefined + } + if *instance.Status == edge.INSTANCESTATUS_ACTIVE || *instance.Status == edge.INSTANCESTATUS_RECONCILING { + return nil + } + return fmt.Errorf("cannot use instance with %s '%s' with status '%s'", identifierType, identifierValue, *instance.Status) +} + +// checkInstanceExistsWithUsableStatus checks if the instance with the given instanceId exists and has a usable status. +func checkInstanceExistsWithUsableStatus(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, instanceId string) error { + return checkInstanceUsableStatus( + ctx, + func(ctx context.Context) (*edge.Instance, error) { + return a.GetInstanceExecute(ctx, projectId, regionId, instanceId) + }, + "ID", + instanceId, + ) +} + +// checkInstanceNameExistsWithUsableStatus checks if the instance with the given displayName exists and has a usable status. +func checkInstanceNameExistsWithUsableStatus(ctx context.Context, a EdgeCloudApiClient, projectId, regionId, displayName string) error { + return checkInstanceUsableStatus( + ctx, + func(ctx context.Context) (*edge.Instance, error) { + return a.GetInstanceByNameExecute(ctx, projectId, regionId, displayName) + }, + "name", + displayName, + ) +} diff --git a/services/edge/wait/wait_test.go b/services/edge/wait/wait_test.go new file mode 100644 index 000000000..f6ca6745e --- /dev/null +++ b/services/edge/wait/wait_test.go @@ -0,0 +1,425 @@ +package wait + +import ( + "context" + "errors" + "net/http" + "strings" + "testing" + "time" + + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/wait" + "github.com/stackitcloud/stackit-sdk-go/services/edge" +) + +const ( + testRegion = "eu01" + testProjectId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + testInstanceId = "xxxxxxxx-xxxxxxx" + testName = "xxxxxxxx" +) + +type mockApiClient struct { + instanceStatus edge.InstanceStatus + instanceError error + + deleteErrors []error + deleteCallCount int + + resourceErrors []error + resourceCallCount int + mockShouldTimeout bool +} + +func (a *mockApiClient) getDeleteError() error { + var err error + if a.deleteCallCount < len(a.deleteErrors) { + err = a.deleteErrors[a.deleteCallCount] + } + a.deleteCallCount++ + return err +} + +func (a *mockApiClient) getResourceError() error { + if a.mockShouldTimeout { + if len(a.resourceErrors) > 0 { + return a.resourceErrors[0] + } + return nil + } + var err error + if a.resourceCallCount < len(a.resourceErrors) { + err = a.resourceErrors[a.resourceCallCount] + } + a.resourceCallCount++ + return err +} + +func (a *mockApiClient) GetInstanceExecute(_ context.Context, _, _, _ string) (*edge.Instance, error) { + if a.deleteErrors != nil { + err := a.getDeleteError() + if err != nil { + return nil, err + } + return &edge.Instance{}, nil + } + + if a.instanceError != nil { + return nil, a.instanceError + } + return &edge.Instance{Status: &a.instanceStatus}, nil +} + +func (a *mockApiClient) GetInstanceByNameExecute(ctx context.Context, _, _, _ string) (*edge.Instance, error) { + return a.GetInstanceExecute(ctx, "", "", "") +} + +type mockGetKubeconfigRequest struct { + client *mockApiClient +} + +func (r *mockGetKubeconfigRequest) ExpirationSeconds(_ int64) edge.ApiGetKubeconfigByInstanceIdRequest { + return r +} + +func (r *mockGetKubeconfigRequest) Execute() (*edge.Kubeconfig, error) { + if err := r.client.getResourceError(); err != nil { + return nil, err + } + return &edge.Kubeconfig{}, nil +} + +func (a *mockApiClient) GetKubeconfigByInstanceId(context.Context, string, string, string) edge.ApiGetKubeconfigByInstanceIdRequest { + return &mockGetKubeconfigRequest{client: a} +} + +type mockGetKubeconfigByNameRequest struct { + client *mockApiClient +} + +func (r *mockGetKubeconfigByNameRequest) ExpirationSeconds(_ int64) edge.ApiGetKubeconfigByInstanceNameRequest { + return r +} + +func (r *mockGetKubeconfigByNameRequest) Execute() (*edge.Kubeconfig, error) { + if err := r.client.getResourceError(); err != nil { + return nil, err + } + return &edge.Kubeconfig{}, nil +} + +func (a *mockApiClient) GetKubeconfigByInstanceName(context.Context, string, string, string) edge.ApiGetKubeconfigByInstanceNameRequest { + return &mockGetKubeconfigByNameRequest{client: a} +} + +type mockGetTokenRequest struct { + client *mockApiClient +} + +func (r *mockGetTokenRequest) ExpirationSeconds(_ int64) edge.ApiGetTokenByInstanceIdRequest { + return r +} + +func (r *mockGetTokenRequest) Execute() (*edge.Token, error) { + if err := r.client.getResourceError(); err != nil { + return nil, err + } + return &edge.Token{}, nil +} + +func (a *mockApiClient) GetTokenByInstanceId(context.Context, string, string, string) edge.ApiGetTokenByInstanceIdRequest { + return &mockGetTokenRequest{client: a} +} + +type mockGetTokenByNameRequest struct { + client *mockApiClient +} + +func (r *mockGetTokenByNameRequest) ExpirationSeconds(_ int64) edge.ApiGetTokenByInstanceNameRequest { + return r +} + +func (r *mockGetTokenByNameRequest) Execute() (*edge.Token, error) { + if err := r.client.getResourceError(); err != nil { + return nil, err + } + return &edge.Token{}, nil +} + +func (a *mockApiClient) GetTokenByInstanceName(context.Context, string, string, string) edge.ApiGetTokenByInstanceNameRequest { + return &mockGetTokenByNameRequest{client: a} +} + +var createOrUpdateInstanceTests = []struct { + desc string + shouldFail bool // This will be used to set instanceError + instanceStatus edge.InstanceStatus + wantErr error +}{ + { + desc: "successful creation", + shouldFail: false, + instanceStatus: edge.INSTANCESTATUS_ACTIVE, + wantErr: nil, + }, + { + desc: "timeout during reconciliation", + shouldFail: false, + instanceStatus: edge.INSTANCESTATUS_RECONCILING, + wantErr: errors.New("WaitWithContext() has timed out"), + }, + { + desc: "failed creation", + shouldFail: false, + instanceStatus: edge.INSTANCESTATUS_ERROR, + wantErr: errors.New("instance creation failed"), + }, + { + desc: "API fails", + shouldFail: true, + wantErr: &oapierror.GenericOpenAPIError{StatusCode: http.StatusInternalServerError}, + }, +} + +var deleteInstanceTests = []struct { + desc string + errorsToReturn []error + wantErr error +}{ + { + desc: "successful deletion", + errorsToReturn: []error{ + nil, + &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}, + }, + wantErr: nil, + }, + { + desc: "timeout during deletion", + errorsToReturn: []error{}, + wantErr: errors.New("WaitWithContext() has timed out"), + }, + { + desc: "API fails with status 500 error", + errorsToReturn: []error{ + &oapierror.GenericOpenAPIError{StatusCode: http.StatusInternalServerError}, + }, + wantErr: &oapierror.GenericOpenAPIError{StatusCode: http.StatusInternalServerError}, + }, +} + +// This test table is for handlers that retry on 404, which is currently required due to incorrect active instance status +var kubeconfigOrTokenTests = []struct { + desc string + instanceStatus edge.InstanceStatus + instanceError error + errorsToReturn []error + mockShouldTimeout bool + wantErr error +}{ + { + desc: "success", + instanceStatus: edge.INSTANCESTATUS_ACTIVE, + errorsToReturn: []error{nil}, + wantErr: nil, + }, + { + desc: "success_on_reconciling", + instanceStatus: edge.INSTANCESTATUS_RECONCILING, + errorsToReturn: []error{nil}, + wantErr: nil, + }, + { + desc: "retry_on_404", + instanceStatus: edge.INSTANCESTATUS_ACTIVE, + errorsToReturn: []error{ + &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}, + &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}, + &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}, + nil, + }, + wantErr: nil, + }, + { + desc: "timeout", + instanceStatus: edge.INSTANCESTATUS_ACTIVE, + errorsToReturn: []error{&oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}}, + mockShouldTimeout: true, + wantErr: errors.New("WaitWithContext() has timed out"), + }, + { + desc: "instance_not_found", + instanceError: &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}, + wantErr: &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}, + }, + { + desc: "unusable_instance_status", + instanceStatus: edge.INSTANCESTATUS_ERROR, + wantErr: errors.New("cannot use instance"), + }, +} + +func checkError(t *testing.T, gotErr, wantErr error) { + t.Helper() + if wantErr == nil { + if gotErr != nil { + t.Fatalf("handler returned an unexpected error: %v", gotErr) + } + return + } + + if gotErr == nil { + t.Fatalf("handler expected error %q, but got nil", wantErr) + } + + var wantOapiErr *oapierror.GenericOpenAPIError + if errors.As(wantErr, &wantOapiErr) { + var gotOapiErr *oapierror.GenericOpenAPIError + if !errors.As(gotErr, &gotOapiErr) { + t.Fatalf("handler error type = %T, want %T", gotErr, wantErr) + } + if wantOapiErr.StatusCode != 0 && gotOapiErr.StatusCode != wantOapiErr.StatusCode { + t.Fatalf("handler error status code = %d, want %d", gotOapiErr.StatusCode, wantOapiErr.StatusCode) + } + return + } + + if !strings.Contains(gotErr.Error(), wantErr.Error()) { + t.Fatalf("handler error = %q, want to contain %q", gotErr.Error(), wantErr.Error()) + } +} + +func executeHandlerAndGetError[T any](handler *wait.AsyncActionHandler[T]) error { + _, err := handler. + SetTimeout(20 * time.Millisecond). + SetThrottle(1 * time.Millisecond). + WaitWithContext(context.Background()) + return err +} + +func TestCreateOrUpdateInstanceWaitHandler(t *testing.T) { + for _, tc := range createOrUpdateInstanceTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + instanceStatus: tc.instanceStatus, + } + if tc.shouldFail { + apiClient.instanceError = &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + } + handler := CreateOrUpdateInstanceWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testInstanceId) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +} + +func TestCreateOrUpdateInstanceByNameWaitHandler(t *testing.T) { + for _, tc := range createOrUpdateInstanceTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + instanceStatus: tc.instanceStatus, + } + if tc.shouldFail { + apiClient.instanceError = &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + } + handler := CreateOrUpdateInstanceByNameWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testName) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +} + +func TestDeleteInstanceWaitHandler(t *testing.T) { + for _, tc := range deleteInstanceTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + deleteErrors: tc.errorsToReturn, + } + handler := DeleteInstanceWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testInstanceId) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +} + +func TestDeleteInstanceByNameWaitHandler(t *testing.T) { + for _, tc := range deleteInstanceTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + deleteErrors: tc.errorsToReturn, + } + handler := DeleteInstanceByNameWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testName) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +} + +func TestKubeconfigWaitHandler(t *testing.T) { + for _, tc := range kubeconfigOrTokenTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + instanceStatus: tc.instanceStatus, + instanceError: tc.instanceError, + resourceErrors: tc.errorsToReturn, + mockShouldTimeout: tc.mockShouldTimeout, + } + handler := KubeconfigWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testInstanceId, nil) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +} + +func TestKubeconfigByInstanceNameWaitHandler(t *testing.T) { + for _, tc := range kubeconfigOrTokenTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + instanceStatus: tc.instanceStatus, + instanceError: tc.instanceError, + resourceErrors: tc.errorsToReturn, + mockShouldTimeout: tc.mockShouldTimeout, + } + handler := KubeconfigByInstanceNameWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testName, nil) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +} + +func TestTokenWaitHandler(t *testing.T) { + for _, tc := range kubeconfigOrTokenTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + instanceStatus: tc.instanceStatus, + instanceError: tc.instanceError, + resourceErrors: tc.errorsToReturn, + mockShouldTimeout: tc.mockShouldTimeout, + } + handler := TokenWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testInstanceId, nil) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +} + +func TestTokenByInstanceNameWaitHandler(t *testing.T) { + for _, tc := range kubeconfigOrTokenTests { + t.Run(tc.desc, func(t *testing.T) { + apiClient := &mockApiClient{ + instanceStatus: tc.instanceStatus, + instanceError: tc.instanceError, + resourceErrors: tc.errorsToReturn, + mockShouldTimeout: tc.mockShouldTimeout, + } + handler := TokenByInstanceNameWaitHandler(context.Background(), apiClient, testProjectId, testRegion, testName, nil) + err := executeHandlerAndGetError(handler) + checkError(t, err, tc.wantErr) + }) + } +}