diff --git a/CHANGELOG.md b/CHANGELOG.md index f1b79a04d..dc80f2645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ - `postgresflex`: [v1.3.0](services/postgresflex/CHANGELOG.md#v130) - **Breaking Change:** The attribute type for `PartialUpdateInstancePayload` and `UpdateInstancePayload` changed from `Storage` to `StorageUpdate`. - **Deprecation:** `StorageUpdate`: updating the performance class field is not possible. +- `intake`: [v0.3.0](services/intake/CHANGELOG.md#v030) + - **Feature:** Add wait handlers for `Intake`, `IntakeRunner`, and `IntakeUser` resources. + - **Improvement:** Add usage examples for the `intake` service. + - `iaas`: - [v1.0.1](services/iaas/CHANGELOG.md#v101) - Bump STACKIT resourcemanager SDK module from `v0.17.1` to `v0.18.0` diff --git a/examples/intake/go.mod b/examples/intake/go.mod new file mode 100644 index 000000000..ea5af500e --- /dev/null +++ b/examples/intake/go.mod @@ -0,0 +1,13 @@ +module github.com/stackitcloud/stackit-sdk-go/examples/intake + +go 1.21 + +require ( + github.com/stackitcloud/stackit-sdk-go/core v0.17.3 + github.com/stackitcloud/stackit-sdk-go/services/intake v0.2.0 +) + +require ( + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect +) diff --git a/examples/intake/go.sum b/examples/intake/go.sum new file mode 100644 index 000000000..b376b1380 --- /dev/null +++ b/examples/intake/go.sum @@ -0,0 +1,10 @@ +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/stackitcloud/stackit-sdk-go/core v0.17.3 h1:GsZGmRRc/3GJLmCUnsZswirr5wfLRrwavbnL/renOqg= +github.com/stackitcloud/stackit-sdk-go/core v0.17.3/go.mod h1:HBCXJGPgdRulplDzhrmwC+Dak9B/x0nzNtmOpu+1Ahg= +github.com/stackitcloud/stackit-sdk-go/services/intake v0.2.0 h1:p/zi4VPoCQWk7/2ubi3hxsqiaye41x/Pl3GXYbPkYOY= +github.com/stackitcloud/stackit-sdk-go/services/intake v0.2.0/go.mod h1:jOArPjNRkwv4487+9ab3dRG+lM09leu5FiRohbQs9Z4= diff --git a/examples/intake/intake.go b/examples/intake/intake.go new file mode 100644 index 000000000..dee1ffe74 --- /dev/null +++ b/examples/intake/intake.go @@ -0,0 +1,86 @@ +package main + +import ( + "context" + "fmt" + "os" + + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/intake" +) + +func main() { + region := "eu01" // Region where the resources will be created + projectId := "PROJECT_ID" // Your STACKIT project ID + + dremioCatalogURI := "DREMIO_CATALOG_URI" // E.g., "https://my-dremio-catalog.data-platform.stackit.run/iceberg" + dremioTokenEndpoint := "DREMIO_TOKEN_ENDPOINT" // E.g., "https://my-dremio.data-platform.stackit.run/oauth/token" + dremioPAT := "DREMIO_PERSONAL_ACCESS_TOKEN" // Your Dremio Personal Access Token + catalogWarehouse := "CATALOG_WAREHOUSE" // Catalog warehouse where the data will be ingested + + intakeUserPassword := "s3cuRe_p@ssW0rd_f0r_1ntake!" // A secure password for the new intake user + + ctx := context.Background() + + intakeClient, err := intake.NewAPIClient( + sdkConfig.WithRegion(region), + ) + if err != nil { + fmt.Fprintf(os.Stderr, "Creating API client: %v\n", err) + os.Exit(1) + } + + // Create an Intake Runner + createRunnerPayload := intake.CreateIntakeRunnerPayload{ + DisplayName: utils.Ptr("my-example-runner"), + MaxMessageSizeKiB: utils.Ptr(int64(10)), + MaxMessagesPerHour: utils.Ptr(int64(1000)), + } + createRunnerResp, err := intakeClient.CreateIntakeRunner(ctx, projectId, region).CreateIntakeRunnerPayload(createRunnerPayload).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating Intake Runner: %v\n", err) + os.Exit(1) + } + intakeRunnerId := *createRunnerResp.Id + fmt.Printf("Triggered creation of Intake Runner with ID: %s. Waiting for it to become active...\n", intakeRunnerId) + + // Create an Intake + dremioAuthType := intake.CatalogAuthType("dremio") // can also be set to "none" if the catalog is not authenticated + createIntakePayload := intake.CreateIntakePayload{ + DisplayName: utils.Ptr("my-example-intake"), + IntakeRunnerId: utils.Ptr(intakeRunnerId), + Catalog: &intake.IntakeCatalog{ + Uri: utils.Ptr(dremioCatalogURI), + Warehouse: utils.Ptr(catalogWarehouse), + Namespace: utils.Ptr("example_namespace"), + TableName: utils.Ptr("example_table"), + Auth: &intake.CatalogAuth{ + Type: &dremioAuthType, + Dremio: &intake.DremioAuth{ + TokenEndpoint: utils.Ptr(dremioTokenEndpoint), + PersonalAccessToken: utils.Ptr(dremioPAT), + }, + }, + }, + } + createIntakeResp, err := intakeClient.CreateIntake(ctx, projectId, region).CreateIntakePayload(createIntakePayload).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating Intake: %v\n", err) + os.Exit(1) + } + intakeId := *createIntakeResp.Id + fmt.Printf("Triggered creation of Intake with ID: %s. Waiting for it to become active...\n", intakeRunnerId) + + createIntakeUserPayload := intake.CreateIntakeUserPayload{ + DisplayName: utils.Ptr("my-example-user"), + Password: utils.Ptr(intakeUserPassword), + } + createIntakeUserResp, err := intakeClient.CreateIntakeUser(ctx, projectId, region, intakeId).CreateIntakeUserPayload(createIntakeUserPayload).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating Intake User: %v\n", err) + os.Exit(1) + } + intakeUserId := *createIntakeUserResp.Id + fmt.Printf("Created Intake User with ID: %s\n", intakeUserId) +} diff --git a/go.work b/go.work index 478149caf..839083073 100644 --- a/go.work +++ b/go.work @@ -10,6 +10,7 @@ use ( ./examples/dns ./examples/errorhandling ./examples/iaas + ./examples/intake ./examples/kms ./examples/loadbalancer ./examples/logme diff --git a/services/intake/CHANGELOG.md b/services/intake/CHANGELOG.md index 482590c6e..364cacac9 100644 --- a/services/intake/CHANGELOG.md +++ b/services/intake/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.3.0 +- **Feature:** Add wait handlers for `Intake`, `IntakeRunner`, and `IntakeUser` resources. +- **Improvement:** Add usage examples for the `intake` service. + ## v0.2.0 - **Feature:** Add response `IntakeRunnerResponse` to `UpdateIntakeRunnerExecute` request - **Feature:** Add response `IntakeUserResponse` to `UpdateIntakeUserExecute` request diff --git a/services/intake/VERSION b/services/intake/VERSION index 1474d00f0..268b0334e 100644 --- a/services/intake/VERSION +++ b/services/intake/VERSION @@ -1 +1 @@ -v0.2.0 +v0.3.0 diff --git a/services/intake/go.mod b/services/intake/go.mod index b4ef91bc5..6d177c71d 100644 --- a/services/intake/go.mod +++ b/services/intake/go.mod @@ -3,6 +3,7 @@ module github.com/stackitcloud/stackit-sdk-go/services/intake go 1.21 require ( + github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/stackitcloud/stackit-sdk-go/core v0.17.3 ) diff --git a/services/intake/wait/wait.go b/services/intake/wait/wait.go new file mode 100644 index 000000000..1d76c3cd9 --- /dev/null +++ b/services/intake/wait/wait.go @@ -0,0 +1,162 @@ +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/intake" +) + +type APIClientInterface interface { + GetIntakeRunnerExecute(ctx context.Context, projectId, region, intakeRunnerId string) (*intake.IntakeRunnerResponse, error) + GetIntakeExecute(ctx context.Context, projectId, region, intakeId string) (*intake.IntakeResponse, error) + GetIntakeUserExecute(ctx context.Context, projectId, region, intakeId, intakeUserId string) (*intake.IntakeUserResponse, error) +} + +func CreateOrUpdateIntakeRunnerWaitHandler(ctx context.Context, a APIClientInterface, projectId, region, intakeRunnerId string) *wait.AsyncActionHandler[intake.IntakeRunnerResponse] { + handler := wait.New(func() (waitFinished bool, response *intake.IntakeRunnerResponse, err error) { + runner, err := a.GetIntakeRunnerExecute(ctx, projectId, region, intakeRunnerId) + if err != nil { + return false, nil, err + } + + if runner == nil { + return false, nil, fmt.Errorf("API returned a nil response for Intake Runner %s", intakeRunnerId) + } + + if runner.Id == nil || runner.State == nil { + return false, nil, fmt.Errorf("could not get ID or State from response for Intake Runner %s", intakeRunnerId) + } + + if *runner.Id == intakeRunnerId && *runner.State == intake.INTAKERUNNERRESPONSESTATE_ACTIVE { + return true, runner, nil + } + + // The API does not have a dedicated failure state for this resource, + // so we rely on the timeout for cases where it never becomes active. + return false, nil, nil + }) + handler.SetTimeout(15 * time.Minute) + return handler +} + +func DeleteIntakeRunnerWaitHandler(ctx context.Context, a APIClientInterface, projectId, region, intakeRunnerId string) *wait.AsyncActionHandler[intake.IntakeRunnerResponse] { + handler := wait.New(func() (waitFinished bool, response *intake.IntakeRunnerResponse, err error) { + _, err = a.GetIntakeRunnerExecute(ctx, projectId, region, intakeRunnerId) + if err == nil { + // Resource still exists + return false, nil, nil + } + + var oapiError *oapierror.GenericOpenAPIError + if errors.As(err, &oapiError) { + if oapiError.StatusCode == http.StatusNotFound { + // Success: Resource is gone + return true, nil, nil + } + } + // An unexpected error occurred + return false, nil, err + }) + handler.SetTimeout(15 * time.Minute) + return handler +} + +func CreateOrUpdateIntakeWaitHandler(ctx context.Context, a APIClientInterface, projectId, region, intakeId string) *wait.AsyncActionHandler[intake.IntakeResponse] { + handler := wait.New(func() (waitFinished bool, response *intake.IntakeResponse, err error) { + ik, err := a.GetIntakeExecute(ctx, projectId, region, intakeId) + if err != nil { + return false, nil, err + } + + if ik == nil { + return false, nil, fmt.Errorf("API returned a nil response for Intake %s", intakeId) + } + + if ik.Id == nil || ik.State == nil { + return false, nil, fmt.Errorf("could not get ID or State from response for Intake %s", intakeId) + } + + if *ik.Id == intakeId && *ik.State == intake.INTAKERESPONSESTATE_ACTIVE { + return true, ik, nil + } + + if *ik.Id == intakeId && *ik.State == intake.INTAKERESPONSESTATE_FAILED { + return true, ik, fmt.Errorf("create/update failed for Intake %s", intakeId) + } + + return false, nil, nil + }) + handler.SetTimeout(10 * time.Minute) + return handler +} + +func DeleteIntakeWaitHandler(ctx context.Context, a APIClientInterface, projectId, region, intakeId string) *wait.AsyncActionHandler[intake.IntakeResponse] { + handler := wait.New(func() (waitFinished bool, response *intake.IntakeResponse, err error) { + _, err = a.GetIntakeExecute(ctx, projectId, region, intakeId) + if err == nil { + return false, nil, nil + } + + var oapiError *oapierror.GenericOpenAPIError + if errors.As(err, &oapiError) { + if oapiError.StatusCode == http.StatusNotFound { + return true, nil, nil + } + } + return false, nil, err + }) + handler.SetTimeout(10 * time.Minute) + return handler +} + +func CreateOrUpdateIntakeUserWaitHandler(ctx context.Context, a APIClientInterface, projectId, region, intakeId, intakeUserId string) *wait.AsyncActionHandler[intake.IntakeUserResponse] { + handler := wait.New(func() (waitFinished bool, response *intake.IntakeUserResponse, err error) { + user, err := a.GetIntakeUserExecute(ctx, projectId, region, intakeId, intakeUserId) + if err != nil { + return false, nil, err + } + + if user == nil { + return false, nil, fmt.Errorf("API returned a nil response for Intake User %s", intakeUserId) + } + + if user.Id == nil || user.State == nil { + return false, nil, fmt.Errorf("could not get ID or State from response for Intake User %s", intakeUserId) + } + + if *user.Id == intakeUserId && *user.State == intake.INTAKEUSERRESPONSESTATE_ACTIVE { + return true, user, nil + } + + // The API does not have a dedicated failure state for this resource, we rely on the timeout for cases where + // it never becomes active. + return false, nil, nil + }) + handler.SetTimeout(5 * time.Minute) + return handler +} + +func DeleteIntakeUserWaitHandler(ctx context.Context, a APIClientInterface, projectId, region, intakeId, intakeUserId string) *wait.AsyncActionHandler[intake.IntakeUserResponse] { + handler := wait.New(func() (waitFinished bool, response *intake.IntakeUserResponse, err error) { + _, err = a.GetIntakeUserExecute(ctx, projectId, region, intakeId, intakeUserId) + if err == nil { + return false, nil, nil + } + + var oapiError *oapierror.GenericOpenAPIError + if errors.As(err, &oapiError) { + if oapiError.StatusCode == http.StatusNotFound { + return true, nil, nil + } + } + return false, nil, err + }) + handler.SetTimeout(5 * time.Minute) + return handler +} diff --git a/services/intake/wait/wait_test.go b/services/intake/wait/wait_test.go new file mode 100644 index 000000000..347c055b1 --- /dev/null +++ b/services/intake/wait/wait_test.go @@ -0,0 +1,516 @@ +package wait + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/intake" +) + +// apiClientMocked is a mock of the API client +type apiClientMocked struct { + getRunnerFails bool + getIntakeFails bool + getUserFails bool + getErrorCode int + returnRunner bool + returnIntake bool + returnUser bool + intakeRunnerResponse *intake.IntakeRunnerResponse + intakeResponse *intake.IntakeResponse + intakeUserResponse *intake.IntakeUserResponse +} + +func (a *apiClientMocked) GetIntakeRunnerExecute(_ context.Context, _, _, _ string) (*intake.IntakeRunnerResponse, error) { + if a.getRunnerFails { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: a.getErrorCode, + } + } + if !a.returnRunner { + return nil, nil + } + return a.intakeRunnerResponse, nil +} + +func (a *apiClientMocked) GetIntakeExecute(_ context.Context, _, _, _ string) (*intake.IntakeResponse, error) { + if a.getIntakeFails { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: a.getErrorCode, + } + } + if !a.returnIntake { + return nil, nil + } + return a.intakeResponse, nil +} + +func (a *apiClientMocked) GetIntakeUserExecute(_ context.Context, _, _, _, _ string) (*intake.IntakeUserResponse, error) { + if a.getUserFails { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: a.getErrorCode, + } + } + if !a.returnUser { + return nil, nil + } + return a.intakeUserResponse, nil +} + +const region = "eu01" + +var ( + projectId = uuid.NewString() + intakeRunnerId = uuid.NewString() + intakeId = uuid.NewString() + intakeUserId = uuid.NewString() +) + +func TestCreateOrUpdateIntakeRunnerWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + getErrorCode int + wantErr bool + wantResp bool + returnRunner bool + intakeRunnerResponse *intake.IntakeRunnerResponse + }{ + { + desc: "succeeded", + getFails: false, + wantErr: false, + wantResp: true, + returnRunner: true, + intakeRunnerResponse: &intake.IntakeRunnerResponse{ + Id: utils.Ptr(intakeRunnerId), + State: utils.Ptr(intake.INTAKERUNNERRESPONSESTATE_ACTIVE), + }, + }, + { + desc: "get fails", + getFails: true, + getErrorCode: http.StatusInternalServerError, + wantErr: true, + wantResp: false, + returnRunner: false, + }, + { + desc: "timeout", + getFails: false, + wantErr: true, + wantResp: false, + returnRunner: true, + intakeRunnerResponse: &intake.IntakeRunnerResponse{ + Id: utils.Ptr(intakeRunnerId), + State: utils.Ptr(intake.INTAKERUNNERRESPONSESTATE_RECONCILING), + }, + }, + { + desc: "nil response", + getFails: false, + wantErr: true, + wantResp: false, + returnRunner: false, + }, + { + desc: "nil id in response", + getFails: false, + wantErr: true, + wantResp: false, + returnRunner: true, + intakeRunnerResponse: &intake.IntakeRunnerResponse{ + State: utils.Ptr(intake.INTAKERUNNERRESPONSESTATE_RECONCILING), + }, + }, + { + desc: "nil state in response", + getFails: false, + wantErr: true, + wantResp: false, + returnRunner: true, + intakeRunnerResponse: &intake.IntakeRunnerResponse{ + Id: utils.Ptr(intakeRunnerId), + }, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getRunnerFails: tt.getFails, + getErrorCode: tt.getErrorCode, + returnRunner: tt.returnRunner, + intakeRunnerResponse: tt.intakeRunnerResponse, + } + + var wantResp *intake.IntakeRunnerResponse + if tt.wantResp { + wantResp = tt.intakeRunnerResponse + } + + handler := CreateOrUpdateIntakeRunnerWaitHandler(context.Background(), apiClient, projectId, region, intakeRunnerId) + got, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + if !cmp.Equal(got, wantResp) { + t.Fatalf("handler got = %v, want %v", got, wantResp) + } + }) + } +} + +func TestDeleteIntakeRunnerWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + getErrorCode int + wantErr bool + returnRunner bool + }{ + { + desc: "succeeded", + getFails: true, + getErrorCode: http.StatusNotFound, + wantErr: false, + returnRunner: false, + }, + { + desc: "get fails", + getFails: true, + getErrorCode: http.StatusInternalServerError, + wantErr: true, + returnRunner: false, + }, + { + desc: "timeout", + getFails: false, + wantErr: true, + returnRunner: true, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getRunnerFails: tt.getFails, + getErrorCode: tt.getErrorCode, + returnRunner: tt.returnRunner, + intakeRunnerResponse: &intake.IntakeRunnerResponse{ // This is only used in the timeout case + Id: utils.Ptr(intakeRunnerId), + }, + } + handler := DeleteIntakeRunnerWaitHandler(context.Background(), apiClient, projectId, region, intakeRunnerId) + _, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCreateOrUpdateIntakeWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + getErrorCode int + wantErr bool + wantResp bool + returnIntake bool + intakeResponse *intake.IntakeResponse + }{ + { + desc: "succeeded", + getFails: false, + wantErr: false, + wantResp: true, + returnIntake: true, + intakeResponse: &intake.IntakeResponse{ + Id: utils.Ptr(intakeId), + State: utils.Ptr(intake.INTAKERESPONSESTATE_ACTIVE), + }, + }, + { + desc: "failed state", + getFails: false, + wantErr: true, + wantResp: true, + returnIntake: true, + intakeResponse: &intake.IntakeResponse{ + Id: utils.Ptr(intakeId), + State: utils.Ptr(intake.INTAKERESPONSESTATE_FAILED), + }, + }, + { + desc: "get fails", + getFails: true, + getErrorCode: http.StatusInternalServerError, + wantErr: true, + wantResp: false, + returnIntake: false, + }, + { + desc: "timeout", + getFails: false, + wantErr: true, + wantResp: false, + returnIntake: true, + intakeResponse: &intake.IntakeResponse{ + Id: utils.Ptr(intakeId), + State: utils.Ptr(intake.INTAKERESPONSESTATE_RECONCILING), + }, + }, + { + desc: "nil response", + getFails: false, + wantErr: true, + wantResp: false, + returnIntake: false, + }, + { + desc: "nil id in response", + getFails: false, + wantErr: true, + wantResp: false, + returnIntake: true, + intakeResponse: &intake.IntakeResponse{ + State: utils.Ptr(intake.INTAKERESPONSESTATE_RECONCILING), + }, + }, + { + desc: "nil state in response", + getFails: false, + wantErr: true, + wantResp: false, + returnIntake: true, + intakeResponse: &intake.IntakeResponse{ + Id: utils.Ptr(intakeId), + }, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getIntakeFails: tt.getFails, + getErrorCode: tt.getErrorCode, + returnIntake: tt.returnIntake, + intakeResponse: tt.intakeResponse, + } + + var wantResp *intake.IntakeResponse + if tt.wantResp { + wantResp = tt.intakeResponse + } + + handler := CreateOrUpdateIntakeWaitHandler(context.Background(), apiClient, projectId, region, intakeId) + got, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + if !cmp.Equal(got, wantResp) { + t.Fatalf("handler got = %v, want %v", got, wantResp) + } + }) + } +} + +func TestDeleteIntakeWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + getErrorCode int + wantErr bool + returnIntake bool + }{ + { + desc: "succeeded", + getFails: true, + getErrorCode: http.StatusNotFound, + wantErr: false, + returnIntake: false, + }, + { + desc: "get fails", + getFails: true, + getErrorCode: http.StatusInternalServerError, + wantErr: true, + returnIntake: false, + }, + { + desc: "timeout", + getFails: false, + wantErr: true, + returnIntake: true, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getIntakeFails: tt.getFails, + getErrorCode: tt.getErrorCode, + returnIntake: tt.returnIntake, + intakeResponse: &intake.IntakeResponse{ + Id: utils.Ptr(intakeId), + }, + } + handler := DeleteIntakeWaitHandler(context.Background(), apiClient, projectId, region, intakeId) + _, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCreateOrUpdateIntakeUserWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + getErrorCode int + wantErr bool + wantResp bool + returnUser bool + intakeUserResponse *intake.IntakeUserResponse + }{ + { + desc: "succeeded", + getFails: false, + wantErr: false, + wantResp: true, + returnUser: true, + intakeUserResponse: &intake.IntakeUserResponse{ + Id: utils.Ptr(intakeUserId), + State: utils.Ptr(intake.INTAKEUSERRESPONSESTATE_ACTIVE), + }, + }, + { + desc: "get fails", + getFails: true, + getErrorCode: http.StatusInternalServerError, + wantErr: true, + wantResp: false, + returnUser: false, + }, + { + desc: "timeout", + getFails: false, + wantErr: true, + wantResp: false, + returnUser: true, + intakeUserResponse: &intake.IntakeUserResponse{ + Id: utils.Ptr(intakeUserId), + State: utils.Ptr(intake.INTAKEUSERRESPONSESTATE_RECONCILING), + }, + }, + { + desc: "nil response", + getFails: false, + wantErr: true, + wantResp: false, + returnUser: false, + }, + { + desc: "nil id in response", + getFails: false, + wantErr: true, + wantResp: false, + returnUser: true, + intakeUserResponse: &intake.IntakeUserResponse{ + State: utils.Ptr(intake.INTAKEUSERRESPONSESTATE_RECONCILING), + }, + }, + { + desc: "nil state in response", + getFails: false, + wantErr: true, + wantResp: false, + returnUser: true, + intakeUserResponse: &intake.IntakeUserResponse{ + Id: utils.Ptr(intakeUserId), + }, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getUserFails: tt.getFails, + getErrorCode: tt.getErrorCode, + returnUser: tt.returnUser, + intakeUserResponse: tt.intakeUserResponse, + } + + var wantResp *intake.IntakeUserResponse + if tt.wantResp { + wantResp = tt.intakeUserResponse + } + + handler := CreateOrUpdateIntakeUserWaitHandler(context.Background(), apiClient, projectId, region, intakeId, intakeUserId) + got, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + if !cmp.Equal(got, wantResp) { + t.Fatalf("handler got = %v, want %v", got, wantResp) + } + }) + } +} + +func TestDeleteIntakeUserWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + getErrorCode int + wantErr bool + returnUser bool + }{ + { + desc: "succeeded", + getFails: true, + getErrorCode: http.StatusNotFound, + wantErr: false, + returnUser: false, + }, + { + desc: "get fails", + getFails: true, + getErrorCode: http.StatusInternalServerError, + wantErr: true, + returnUser: false, + }, + { + desc: "timeout", + getFails: false, + wantErr: true, + returnUser: true, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getUserFails: tt.getFails, + getErrorCode: tt.getErrorCode, + returnUser: tt.returnUser, + intakeUserResponse: &intake.IntakeUserResponse{ + Id: utils.Ptr(intakeUserId), + }, + } + handler := DeleteIntakeUserWaitHandler(context.Background(), apiClient, projectId, region, intakeId, intakeUserId) + _, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}