From 546eafcb2fdbfeb4282bc8307906647d160fd373 Mon Sep 17 00:00:00 2001 From: Andre Harms Date: Thu, 5 Feb 2026 16:07:23 +0100 Subject: [PATCH] feat: refactor Terraform ID handling and add user mapping functions --- .../postgresflexalpha/database/datasource.go | 4 +- .../postgresflexalpha/database/resource.go | 85 +-- .../services/postgresflexalpha/user/mapper.go | 150 +++++ .../postgresflexalpha/user/mapper_test.go | 632 ++++++++++++++++++ 4 files changed, 816 insertions(+), 55 deletions(-) create mode 100644 stackit/internal/services/postgresflexalpha/user/mapper.go create mode 100644 stackit/internal/services/postgresflexalpha/user/mapper_test.go diff --git a/stackit/internal/services/postgresflexalpha/database/datasource.go b/stackit/internal/services/postgresflexalpha/database/datasource.go index 9212bb0a..3e526242 100644 --- a/stackit/internal/services/postgresflexalpha/database/datasource.go +++ b/stackit/internal/services/postgresflexalpha/database/datasource.go @@ -27,7 +27,7 @@ type DataSourceModel struct { InstanceId types.String `tfsdk:"instance_id"` Region types.String `tfsdk:"region"` DatabaseID types.Int64 `tfsdk:"database_id"` - TerraformID types.String `tfsdk:"tf_id"` + TerraformID types.String `tfsdk:"id"` } // Ensure the implementation satisfies the expected interfaces. @@ -99,7 +99,7 @@ func (r *databaseDataSource) Schema(ctx context.Context, _ datasource.SchemaRequ Optional: true, Computed: true, } - s.Attributes["tf_id"] = schema.StringAttribute{ + s.Attributes["id"] = schema.StringAttribute{ Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`," + "`database_id`\\\".\",", Optional: true, diff --git a/stackit/internal/services/postgresflexalpha/database/resource.go b/stackit/internal/services/postgresflexalpha/database/resource.go index ca8230a3..169c3e99 100644 --- a/stackit/internal/services/postgresflexalpha/database/resource.go +++ b/stackit/internal/services/postgresflexalpha/database/resource.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "net/http" + "strconv" "strings" "github.com/hashicorp/terraform-plugin-framework/path" @@ -24,7 +25,6 @@ import ( "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database/resources_gen" postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils" - "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" ) // Ensure the implementation satisfies the expected interfaces. @@ -34,12 +34,14 @@ var ( _ resource.ResourceWithImportState = &databaseResource{} _ resource.ResourceWithModifyPlan = &databaseResource{} _ resource.ResourceWithIdentity = &databaseResource{} + + errDatabaseNotFound = errors.New("database not found") ) // ResourceModel describes the resource data model. type ResourceModel struct { postgresflexalpha2.DatabaseModel - TerraformID types.String `tfsdk:"tf_id"` + TerraformID types.String `tfsdk:"id"` DatabaseID types.Int64 `tfsdk:"database_id"` } @@ -126,7 +128,7 @@ var modifiersFileByte []byte // Schema defines the schema for the resource. func (r *databaseResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { s := postgresflexalpha2.DatabaseResourceSchema(ctx) - s.Attributes["tf_id"] = schema.StringAttribute{ + s.Attributes["id"] = schema.StringAttribute{ Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`,`database_id`\\\".\",", Optional: true, Computed: true, @@ -172,7 +174,6 @@ func (r *databaseResource) IdentitySchema( RequiredForImport: true, }, "database_id": identityschema.Int64Attribute{ - // database id RequiredForImport: true, }, }, @@ -508,64 +509,42 @@ func (r *databaseResource) ImportState( return } + databaseId, err := strconv.ParseInt(idParts[3], 10, 64) + if err != nil { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error importing database", + fmt.Sprintf("Invalid database_id format: %q. It must be a valid integer.", idParts[3]), + ) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), idParts[3])...) - core.LogAndAddWarning( - ctx, - &resp.Diagnostics, - "Postgresflex database imported with empty password", - "The database password is not imported as it is only available upon creation of a new database. The password field will be empty.", - ) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), databaseId)...) + + //TODO: Investigate if this logic is still required. + //core.LogAndAddWarning( + // ctx, + // &resp.Diagnostics, + // "Postgresflex database imported with empty password", + // "The database password is not imported as it is only available upon creation of a new database. The password field will be empty.", + //) var identityData DatabaseResourceIdentityModel - resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) + identityData.ProjectID = types.StringValue(idParts[0]) + identityData.Region = types.StringValue(idParts[1]) + identityData.InstanceID = types.StringValue(idParts[2]) + identityData.DatabaseID = types.Int64Value(databaseId) + + resp.Diagnostics.Append(req.Identity.Set(ctx, &identityData)...) + if resp.Diagnostics.HasError() { return } - resp.Diagnostics.Append( - resp.State.SetAttribute( - ctx, - path.Root("tf_id"), - utils.BuildInternalTerraformId( - identityData.ProjectID.ValueString(), - identityData.Region.ValueString(), - identityData.InstanceID.ValueString(), - identityData.DatabaseID.String(), - ), - )..., - ) - resp.Diagnostics.Append( - resp.State.SetAttribute( - ctx, - path.Root("project_id"), - identityData.ProjectID.ValueString(), - )..., - ) - resp.Diagnostics.Append( - resp.State.SetAttribute( - ctx, path.Root("region"), identityData.Region.ValueString(), - )..., - ) - resp.Diagnostics.Append( - resp.State.SetAttribute( - ctx, - path.Root("instance_id"), - identityData.InstanceID.ValueString(), - )..., - ) - resp.Diagnostics.Append( - resp.State.SetAttribute( - ctx, - path.Root("database_id"), - identityData.DatabaseID.ValueInt64(), - )..., - ) - tflog.Info(ctx, "Postgres Flex instance state imported") } - -var errDatabaseNotFound = errors.New("database not found") diff --git a/stackit/internal/services/postgresflexalpha/user/mapper.go b/stackit/internal/services/postgresflexalpha/user/mapper.go new file mode 100644 index 00000000..655d7d80 --- /dev/null +++ b/stackit/internal/services/postgresflexalpha/user/mapper.go @@ -0,0 +1,150 @@ +package postgresflexalpha + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" + "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion" + "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" + "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" +) + +// mapDataSourceFields maps API response to data source model, preserving existing ID. +func mapDataSourceFields(userResp *postgresflex.GetUserResponse, model *DataSourceModel, region string) error { + if userResp == nil { + return fmt.Errorf("response is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + user := userResp + + var userId int64 + if model.UserId.ValueInt64() != 0 { + userId = model.UserId.ValueInt64() + } else if user.Id != nil { + userId = *user.Id + } else { + return fmt.Errorf("user id not present") + } + + model.TerraformID = utils.BuildInternalTerraformId( + model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), strconv.FormatInt(userId, 10), + ) + + model.UserId = types.Int64Value(userId) + model.Name = types.StringValue(user.GetName()) + + if user.Roles == nil { + model.Roles = types.List(types.SetNull(types.StringType)) + } else { + var roles []attr.Value + for _, role := range *user.Roles { + roles = append(roles, types.StringValue(string(role))) + } + rolesSet, diags := types.SetValue(types.StringType, roles) + if diags.HasError() { + return fmt.Errorf("failed to map roles: %w", core.DiagsToError(diags)) + } + model.Roles = types.List(rolesSet) + } + + model.Id = types.Int64Value(userId) + model.Host = types.StringValue(user.GetHost()) + model.Port = types.Int64Value(user.GetPort()) + model.Region = types.StringValue(region) + model.Status = types.StringValue(user.GetStatus()) + model.ConnectionString = types.StringValue(user.GetConnectionString()) + return nil +} + +// toPayloadRoles converts a string slice to the API's role type. +func toPayloadRoles(roles *[]string) *[]postgresflex.UserRole { + var userRoles = make([]postgresflex.UserRole, 0, len(*roles)) + for _, role := range *roles { + userRoles = append(userRoles, postgresflex.UserRole(role)) + } + return &userRoles +} + +// toUpdatePayload creates an API update payload from the resource model. +func toUpdatePayload(model *Model, roles *[]string) ( + *postgresflex.UpdateUserRequestPayload, + error, +) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + if roles == nil { + return nil, fmt.Errorf("nil roles") + } + + return &postgresflex.UpdateUserRequestPayload{ + Name: conversion.StringValueToPointer(model.Name), + Roles: toPayloadRoles(roles), + }, nil +} + +// toCreatePayload creates an API create payload from the resource model. +func toCreatePayload(model *Model, roles *[]string) (*postgresflex.CreateUserRequestPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + if roles == nil { + return nil, fmt.Errorf("nil roles") + } + + return &postgresflex.CreateUserRequestPayload{ + Roles: toPayloadRoles(roles), + Name: conversion.StringValueToPointer(model.Name), + }, nil +} + +// mapResourceFields maps API response to the resource model, preserving existing ID. +func mapResourceFields(userResp *postgresflex.GetUserResponse, model *Model, region string) error { + if userResp == nil { + return fmt.Errorf("response is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + user := userResp + + var userId int64 + if model.UserId.ValueInt64() != 0 { + userId = model.UserId.ValueInt64() + } else if user.Id != nil { + userId = *user.Id + } else { + return fmt.Errorf("user id not present") + } + model.TerraformID = utils.BuildInternalTerraformId( + model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), strconv.FormatInt(userId, 10), + ) + model.Id = types.Int64Value(userId) + model.UserId = types.Int64Value(userId) + model.Name = types.StringPointerValue(user.Name) + + if user.Roles == nil { + model.Roles = types.List(types.SetNull(types.StringType)) + } else { + var roles []attr.Value + for _, role := range *user.Roles { + roles = append(roles, types.StringValue(string(role))) + } + rolesSet, diags := types.SetValue(types.StringType, roles) + if diags.HasError() { + return fmt.Errorf("failed to map roles: %w", core.DiagsToError(diags)) + } + model.Roles = types.List(rolesSet) + } + model.Host = types.StringPointerValue(user.Host) + model.Port = types.Int64PointerValue(user.Port) + model.Region = types.StringValue(region) + model.Status = types.StringPointerValue(user.Status) + model.ConnectionString = types.StringPointerValue(user.ConnectionString) + return nil +} diff --git a/stackit/internal/services/postgresflexalpha/user/mapper_test.go b/stackit/internal/services/postgresflexalpha/user/mapper_test.go new file mode 100644 index 00000000..863dbbcc --- /dev/null +++ b/stackit/internal/services/postgresflexalpha/user/mapper_test.go @@ -0,0 +1,632 @@ +package postgresflexalpha + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" + data "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user/datasources_gen" + resource "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user/resources_gen" +) + +func TestMapDataSourceFields(t *testing.T) { + const testRegion = "region" + tests := []struct { + description string + input *postgresflex.GetUserResponse + region string + expected DataSourceModel + isValid bool + }{ + { + "default_values", + &postgresflex.GetUserResponse{}, + testRegion, + DataSourceModel{ + UserModel: data.UserModel{ + Id: types.Int64Value(1), + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringValue(""), + Roles: types.List(types.SetNull(types.StringType)), + Host: types.StringValue(""), + Port: types.Int64Value(0), + Status: types.StringValue(""), + Region: types.StringValue(testRegion), + ConnectionString: types.StringValue(""), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "simple_values", + &postgresflex.GetUserResponse{ + Roles: &[]postgresflex.UserRole{ + "role_1", + "role_2", + "", + }, + Name: utils.Ptr("username"), + Host: utils.Ptr("host"), + Port: utils.Ptr(int64(1234)), + }, + testRegion, + DataSourceModel{ + + UserModel: data.UserModel{ + Id: types.Int64Value(1), + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringValue("username"), + Roles: types.List( + types.SetValueMust( + types.StringType, []attr.Value{ + types.StringValue("role_1"), + types.StringValue("role_2"), + types.StringValue(""), + }, + ), + ), + Host: types.StringValue("host"), + Port: types.Int64Value(1234), + Region: types.StringValue(testRegion), + Status: types.StringValue(""), + ConnectionString: types.StringValue(""), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "null_fields_and_int_conversions", + &postgresflex.GetUserResponse{ + Id: utils.Ptr(int64(1)), + Roles: &[]postgresflex.UserRole{}, + Name: nil, + Host: nil, + Port: utils.Ptr(int64(2123456789)), + Status: utils.Ptr("status"), + ConnectionString: utils.Ptr("connection_string"), + }, + testRegion, + DataSourceModel{ + UserModel: data.UserModel{ + Id: types.Int64Value(1), + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringValue(""), + Roles: types.List(types.SetValueMust(types.StringType, []attr.Value{})), + Host: types.StringValue(""), + Port: types.Int64Value(2123456789), + Region: types.StringValue(testRegion), + Status: types.StringValue("status"), + ConnectionString: types.StringValue("connection_string"), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "nil_response", + nil, + testRegion, + DataSourceModel{}, + false, + }, + { + "nil_response_2", + &postgresflex.GetUserResponse{}, + testRegion, + DataSourceModel{}, + false, + }, + { + "no_resource_id", + &postgresflex.GetUserResponse{}, + testRegion, + DataSourceModel{}, + false, + }, + } + for _, tt := range tests { + t.Run( + tt.description, func(t *testing.T) { + state := &DataSourceModel{ + UserModel: data.UserModel{ + ProjectId: tt.expected.ProjectId, + InstanceId: tt.expected.InstanceId, + UserId: tt.expected.UserId, + }, + } + err := mapDataSourceFields(tt.input, state, tt.region) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(state, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }, + ) + } +} + +func TestMapFieldsCreate(t *testing.T) { + const testRegion = "region" + tests := []struct { + description string + input *postgresflex.GetUserResponse + region string + expected Model + isValid bool + }{ + { + "default_values", + &postgresflex.GetUserResponse{ + Id: utils.Ptr(int64(1)), + }, + testRegion, + Model{ + UserModel: resource.UserModel{ + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringNull(), + Roles: types.List(types.SetNull(types.StringType)), + Password: types.StringNull(), + Host: types.StringNull(), + Port: types.Int64Null(), + Region: types.StringValue(testRegion), + Status: types.StringNull(), + ConnectionString: types.StringNull(), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "simple_values", + &postgresflex.GetUserResponse{ + Id: utils.Ptr(int64(1)), + Name: utils.Ptr("username"), + ConnectionString: utils.Ptr("connection_string"), + Status: utils.Ptr("status"), + }, + testRegion, + Model{ + UserModel: resource.UserModel{ + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringValue("username"), + Roles: types.List(types.SetNull(types.StringType)), + Password: types.StringNull(), + Host: types.StringNull(), + Port: types.Int64Null(), + Region: types.StringValue(testRegion), + Status: types.StringValue("status"), + ConnectionString: types.StringValue("connection_string"), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "null_fields_and_int_conversions", + &postgresflex.GetUserResponse{ + Id: utils.Ptr(int64(1)), + Name: nil, + ConnectionString: nil, + Status: nil, + }, + testRegion, + Model{ + UserModel: resource.UserModel{ + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringNull(), + Roles: types.List(types.SetNull(types.StringType)), + Password: types.StringNull(), + Host: types.StringNull(), + Port: types.Int64Null(), + Region: types.StringValue(testRegion), + Status: types.StringNull(), + ConnectionString: types.StringNull(), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "nil_response", + nil, + testRegion, + Model{}, + false, + }, + { + "nil_response_2", + &postgresflex.GetUserResponse{}, + testRegion, + Model{}, + false, + }, + { + "no_resource_id", + &postgresflex.GetUserResponse{}, + testRegion, + Model{}, + false, + }, + } + for _, tt := range tests { + t.Run( + tt.description, func(t *testing.T) { + state := &Model{ + UserModel: resource.UserModel{ + ProjectId: tt.expected.ProjectId, + InstanceId: tt.expected.InstanceId, + }, + } + + err := mapResourceFields(tt.input, state, tt.region) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(state, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }, + ) + } +} + +func TestMapFields(t *testing.T) { + const testRegion = "region" + tests := []struct { + description string + input *postgresflex.GetUserResponse + region string + expected Model + isValid bool + }{ + { + "default_values", + &postgresflex.GetUserResponse{ + Id: utils.Ptr(int64(1)), + }, + testRegion, + Model{ + UserModel: resource.UserModel{ + Id: types.Int64Value(1), + UserId: types.Int64Value(int64(1)), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringNull(), + Roles: types.List(types.SetNull(types.StringType)), + Host: types.StringNull(), + Port: types.Int64Null(), + Region: types.StringValue(testRegion), + Status: types.StringNull(), + ConnectionString: types.StringNull(), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "simple_values", + &postgresflex.GetUserResponse{ + Id: utils.Ptr(int64(1)), + Roles: &[]postgresflex.UserRole{ + "role_1", + "role_2", + "", + }, + Name: utils.Ptr("username"), + Host: utils.Ptr("host"), + Port: utils.Ptr(int64(1234)), + }, + testRegion, + Model{ + UserModel: resource.UserModel{ + Id: types.Int64Value(1), + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringValue("username"), + Roles: types.List( + types.SetValueMust( + types.StringType, []attr.Value{ + types.StringValue("role_1"), + types.StringValue("role_2"), + types.StringValue(""), + }, + ), + ), + Host: types.StringValue("host"), + Port: types.Int64Value(1234), + Region: types.StringValue(testRegion), + Status: types.StringNull(), + ConnectionString: types.StringNull(), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "null_fields_and_int_conversions", + &postgresflex.GetUserResponse{ + Id: utils.Ptr(int64(1)), + Name: nil, + Host: nil, + Port: utils.Ptr(int64(2123456789)), + }, + testRegion, + Model{ + UserModel: resource.UserModel{ + Id: types.Int64Value(1), + UserId: types.Int64Value(1), + InstanceId: types.StringValue("iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringNull(), + Roles: types.List(types.SetNull(types.StringType)), + Host: types.StringNull(), + Port: types.Int64Value(2123456789), + Region: types.StringValue(testRegion), + Status: types.StringNull(), + ConnectionString: types.StringNull(), + }, + TerraformID: types.StringValue("pid,region,iid,1"), + }, + true, + }, + { + "nil_response", + nil, + testRegion, + Model{}, + false, + }, + { + "nil_response_2", + &postgresflex.GetUserResponse{}, + testRegion, + Model{}, + false, + }, + { + "no_resource_id", + &postgresflex.GetUserResponse{}, + testRegion, + Model{}, + false, + }, + } + for _, tt := range tests { + t.Run( + tt.description, func(t *testing.T) { + state := &Model{ + UserModel: resource.UserModel{ + ProjectId: tt.expected.ProjectId, + InstanceId: tt.expected.InstanceId, + }, + } + err := mapResourceFields(tt.input, state, tt.region) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(state, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }, + ) + } +} + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + input *Model + inputRoles *[]string + expected *postgresflex.CreateUserRequestPayload + isValid bool + }{ + { + "default_values", + &Model{}, + &[]string{}, + &postgresflex.CreateUserRequestPayload{ + Name: nil, + Roles: &[]postgresflex.UserRole{}, + }, + true, + }, + { + "simple_values", + &Model{ + UserModel: resource.UserModel{ + Name: types.StringValue("username"), + }, + }, + &[]string{ + "role_1", + "role_2", + }, + &postgresflex.CreateUserRequestPayload{ + Name: utils.Ptr("username"), + Roles: &[]postgresflex.UserRole{ + "role_1", + "role_2", + }, + }, + true, + }, + { + "null_fields_and_int_conversions", + &Model{ + UserModel: resource.UserModel{ + Name: types.StringNull(), + }, + }, + &[]string{ + "", + }, + &postgresflex.CreateUserRequestPayload{ + Roles: &[]postgresflex.UserRole{ + "", + }, + Name: nil, + }, + true, + }, + { + "nil_model", + nil, + &[]string{}, + nil, + false, + }, + { + "nil_roles", + &Model{}, + nil, + nil, + false, + }, + } + for _, tt := range tests { + t.Run( + tt.description, func(t *testing.T) { + output, err := toCreatePayload(tt.input, tt.inputRoles) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }, + ) + } +} + +func TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + input *Model + inputRoles *[]string + expected *postgresflex.UpdateUserRequestPayload + isValid bool + }{ + { + "default_values", + &Model{}, + &[]string{}, + &postgresflex.UpdateUserRequestPayload{ + Roles: &[]postgresflex.UserRole{}, + }, + true, + }, + { + "default_values", + &Model{ + UserModel: resource.UserModel{ + Name: types.StringValue("username"), + }, + }, + &[]string{ + "role_1", + "role_2", + }, + &postgresflex.UpdateUserRequestPayload{ + Name: utils.Ptr("username"), + Roles: &[]postgresflex.UserRole{ + "role_1", + "role_2", + }, + }, + true, + }, + { + "null_fields_and_int_conversions", + &Model{ + UserModel: resource.UserModel{ + Name: types.StringNull(), + }, + }, + &[]string{ + "", + }, + &postgresflex.UpdateUserRequestPayload{ + Roles: &[]postgresflex.UserRole{ + "", + }, + }, + true, + }, + { + "nil_model", + nil, + &[]string{}, + nil, + false, + }, + { + "nil_roles", + &Model{}, + nil, + nil, + false, + }, + } + for _, tt := range tests { + t.Run( + tt.description, func(t *testing.T) { + output, err := toUpdatePayload(tt.input, tt.inputRoles) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }, + ) + } +}