chore(projectmanager): remove members attribute (#737)

relates to STACKITTPR-165
This commit is contained in:
Ruben Hönle 2025-04-01 09:33:31 +02:00 committed by GitHub
parent a8809a4979
commit 297a8a6f88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 145 additions and 713 deletions

View file

@ -2,15 +2,13 @@ package project
import (
"context"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/authorization"
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
)
@ -38,7 +36,6 @@ func TestMapProjectFields(t *testing.T) {
ProjectId: types.StringValue("pid"),
ContainerParentId: types.StringNull(),
Name: types.StringNull(),
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
},
nil,
true,
@ -65,7 +62,6 @@ func TestMapProjectFields(t *testing.T) {
ProjectId: types.StringValue("pid"),
ContainerParentId: types.StringValue("parent_cid"),
Name: types.StringValue("name"),
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
},
&map[string]string{
"label1": "ref1",
@ -95,7 +91,6 @@ func TestMapProjectFields(t *testing.T) {
ProjectId: types.StringValue("pid"),
ContainerParentId: types.StringValue(testUUID),
Name: types.StringValue("name"),
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
},
&map[string]string{
"label1": "ref1",
@ -138,7 +133,6 @@ func TestMapProjectFields(t *testing.T) {
model := &Model{
ContainerId: tt.expected.ContainerId,
ContainerParentId: containerParentId,
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
}
err := mapProjectFields(context.Background(), tt.projectResp, model, nil)
@ -158,195 +152,22 @@ func TestMapProjectFields(t *testing.T) {
}
}
func TestMapMembersFields(t *testing.T) {
tests := []struct {
description string
configMembers basetypes.ListValue
membersResp *[]authorization.Member
expected Model
expectedLabels *map[string]string
isValid bool
}{
{
"default_ok",
types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
&[]authorization.Member{
{
Subject: utils.Ptr("owner_email"),
Role: utils.Ptr("owner"),
},
{
Subject: utils.Ptr("reader_email"),
Role: utils.Ptr("reader"),
},
},
Model{
Id: types.StringNull(),
ProjectId: types.StringNull(),
ContainerId: types.StringNull(),
ContainerParentId: types.StringNull(),
Name: types.StringNull(),
Labels: types.MapNull(types.StringType),
Members: types.ListValueMust(types.ObjectType{AttrTypes: memberTypes}, []attr.Value{
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("owner_email"),
"role": types.StringValue("owner"),
},
),
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("reader_email"),
"role": types.StringValue("reader"),
},
),
}),
},
nil,
true,
},
{
"default_ok (preserve model order)",
types.ListValueMust(types.ObjectType{AttrTypes: memberTypes}, []attr.Value{
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("reader_email"),
"role": types.StringValue("reader"),
},
),
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("owner_email"),
"role": types.StringValue("owner"),
},
),
}),
&[]authorization.Member{
{
Subject: utils.Ptr("owner_email"),
Role: utils.Ptr("owner"),
},
{
Subject: utils.Ptr("reader_email"),
Role: utils.Ptr("reader"),
},
},
Model{
Id: types.StringNull(),
ProjectId: types.StringNull(),
ContainerId: types.StringNull(),
ContainerParentId: types.StringNull(),
Name: types.StringNull(),
Labels: types.MapNull(types.StringType),
Members: types.ListValueMust(types.ObjectType{AttrTypes: memberTypes}, []attr.Value{
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("reader_email"),
"role": types.StringValue("reader"),
},
),
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("owner_email"),
"role": types.StringValue("owner"),
},
),
}),
},
nil,
true,
},
{
"empty members",
types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
&[]authorization.Member{},
Model{
Id: types.StringNull(),
ProjectId: types.StringNull(),
ContainerId: types.StringNull(),
ContainerParentId: types.StringNull(),
Name: types.StringNull(),
Labels: types.MapNull(types.StringType),
Members: types.ListValueMust(types.ObjectType{AttrTypes: memberTypes}, []attr.Value{}),
},
nil,
true,
},
{
"nil members",
types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
nil,
Model{
Id: types.StringNull(),
ProjectId: types.StringNull(),
ContainerId: types.StringNull(),
ContainerParentId: types.StringNull(),
Name: types.StringNull(),
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
Labels: types.MapNull(types.StringType),
},
nil,
true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
state := &Model{
Id: types.StringNull(),
ProjectId: types.StringNull(),
ContainerId: types.StringNull(),
ContainerParentId: types.StringNull(),
Name: types.StringNull(),
Labels: types.MapNull(types.StringType),
}
if !tt.configMembers.IsNull() {
state.Members = tt.configMembers
}
err := mapMembersFields(context.Background(), tt.membersResp, state)
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
input *ResourceModel
inputLabels *map[string]string
expected *resourcemanager.CreateProjectPayload
isValid bool
}{
{
"mapping_with_conversions_single_member",
&Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
Members: types.ListValueMust(types.ObjectType{AttrTypes: memberTypes}, []attr.Value{
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("owner_email"),
"role": types.StringValue("owner"),
},
),
}),
"mapping_with_conversions",
&ResourceModel{
Model: Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
},
OwnerEmail: types.StringValue("john.doe@stackit.cloud"),
},
&map[string]string{
"label1": "1",
@ -360,7 +181,7 @@ func TestToCreatePayload(t *testing.T) {
},
Members: &[]resourcemanager.Member{
{
Subject: utils.Ptr("owner_email"),
Subject: utils.Ptr("john.doe@stackit.cloud"),
Role: utils.Ptr("owner"),
},
},
@ -369,119 +190,12 @@ func TestToCreatePayload(t *testing.T) {
true,
},
{
"mapping_with_conversions_ok_multiple_members",
&Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
Members: types.ListValueMust(types.ObjectType{AttrTypes: memberTypes}, []attr.Value{
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("owner_email"),
"role": types.StringValue("owner"),
},
),
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("reader_email"),
"role": types.StringValue("reader"),
},
),
}),
},
&map[string]string{
"label1": "1",
"label2": "2",
},
&resourcemanager.CreateProjectPayload{
ContainerParentId: utils.Ptr("pid"),
Labels: &map[string]string{
"label1": "1",
"label2": "2",
"no owner_email fails",
&ResourceModel{
Model: Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
},
Members: &[]resourcemanager.Member{
{
Subject: utils.Ptr("owner_email"),
Role: utils.Ptr("owner"),
},
{
Subject: utils.Ptr("reader_email"),
Role: utils.Ptr("reader"),
},
},
Name: utils.Ptr("name"),
},
true,
},
{
"new members field takes precedence over deprecated owner_email field",
&Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
OwnerEmail: types.StringValue("some_email_deprecated"),
Members: types.ListValueMust(types.ObjectType{AttrTypes: memberTypes}, []attr.Value{
types.ObjectValueMust(
memberTypes,
map[string]attr.Value{
"subject": types.StringValue("owner_email"),
"role": types.StringValue("owner"),
},
),
}),
},
&map[string]string{
"label1": "1",
"label2": "2",
},
&resourcemanager.CreateProjectPayload{
ContainerParentId: utils.Ptr("pid"),
Labels: &map[string]string{
"label1": "1",
"label2": "2",
},
Members: &[]resourcemanager.Member{
{
Subject: utils.Ptr("owner_email"),
Role: utils.Ptr("owner"),
},
},
Name: utils.Ptr("name"),
},
true,
},
{
"deprecated owner_email field still works",
&Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
OwnerEmail: types.StringValue("some_email_deprecated"),
},
&map[string]string{
"label1": "1",
"label2": "2",
},
&resourcemanager.CreateProjectPayload{
ContainerParentId: utils.Ptr("pid"),
Labels: &map[string]string{
"label1": "1",
"label2": "2",
},
Members: &[]resourcemanager.Member{
{
Subject: utils.Ptr("some_email_deprecated"),
Role: utils.Ptr("owner"),
},
},
Name: utils.Ptr("name"),
},
true,
},
{
"no members or owner_email fails",
&Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
},
&map[string]string{},
nil,
@ -508,7 +222,7 @@ func TestToCreatePayload(t *testing.T) {
tt.input.Labels = convertedLabels
}
}
output, err := toCreatePayload(context.Background(), tt.input)
output, err := toCreatePayload(tt.input)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@ -528,14 +242,14 @@ func TestToCreatePayload(t *testing.T) {
func TestToUpdatePayload(t *testing.T) {
tests := []struct {
description string
input *Model
input *ResourceModel
inputLabels *map[string]string
expected *resourcemanager.PartialUpdateProjectPayload
isValid bool
}{
{
"default_ok",
&Model{},
&ResourceModel{},
nil,
&resourcemanager.PartialUpdateProjectPayload{
ContainerParentId: nil,
@ -546,10 +260,12 @@ func TestToUpdatePayload(t *testing.T) {
},
{
"mapping_with_conversions_ok",
&Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
OwnerEmail: types.StringValue("owner_email"),
&ResourceModel{
Model: Model{
ContainerParentId: types.StringValue("pid"),
Name: types.StringValue("name"),
},
OwnerEmail: types.StringValue("owner_email"),
},
&map[string]string{
"label1": "1",
@ -602,3 +318,57 @@ func TestToUpdatePayload(t *testing.T) {
})
}
}
func TestToMembersPayload(t *testing.T) {
type args struct {
model *ResourceModel
}
tests := []struct {
name string
args args
want *[]resourcemanager.Member
wantErr bool
}{
{
name: "missing model",
args: args{},
want: nil,
wantErr: true,
},
{
name: "empty model",
args: args{
model: &ResourceModel{},
},
want: nil,
wantErr: true,
},
{
name: "ok",
args: args{
model: &ResourceModel{
OwnerEmail: types.StringValue("john.doe@stackit.cloud"),
},
},
want: &[]resourcemanager.Member{
{
Subject: utils.Ptr("john.doe@stackit.cloud"),
Role: utils.Ptr("owner"),
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := toMembersPayload(tt.args.model)
if (err != nil) != tt.wantErr {
t.Errorf("toMembersPayload() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("toMembersPayload() got = %v, want %v", got, tt.want)
}
})
}
}