* Preserve order of project members even if API re-orders them * Adjust role field description * Fix backwards compatibility of deprecated owner_email field * Fix typo
915 lines
25 KiB
Go
915 lines
25 KiB
Go
package project
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
"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/config"
|
|
"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"
|
|
)
|
|
|
|
func TestMapProjectFields(t *testing.T) {
|
|
testUUID := uuid.New().String()
|
|
tests := []struct {
|
|
description string
|
|
uuidContainerParentId bool
|
|
projectResp *resourcemanager.GetProjectResponse
|
|
expected Model
|
|
expectedLabels *map[string]string
|
|
isValid bool
|
|
}{
|
|
{
|
|
"default_ok",
|
|
false,
|
|
&resourcemanager.GetProjectResponse{
|
|
ContainerId: utils.Ptr("cid"),
|
|
ProjectId: utils.Ptr("pid"),
|
|
},
|
|
Model{
|
|
Id: types.StringValue("cid"),
|
|
ContainerId: types.StringValue("cid"),
|
|
ProjectId: types.StringValue("pid"),
|
|
ContainerParentId: types.StringNull(),
|
|
Name: types.StringNull(),
|
|
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
|
|
},
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
"container_parent_id_ok",
|
|
false,
|
|
&resourcemanager.GetProjectResponse{
|
|
ContainerId: utils.Ptr("cid"),
|
|
ProjectId: utils.Ptr("pid"),
|
|
Labels: &map[string]string{
|
|
"label1": "ref1",
|
|
"label2": "ref2",
|
|
},
|
|
Parent: &resourcemanager.Parent{
|
|
ContainerId: utils.Ptr("parent_cid"),
|
|
Id: utils.Ptr("parent_pid"),
|
|
},
|
|
Name: utils.Ptr("name"),
|
|
},
|
|
Model{
|
|
Id: types.StringValue("cid"),
|
|
ContainerId: types.StringValue("cid"),
|
|
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",
|
|
"label2": "ref2",
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"uuid_parent_id_ok",
|
|
true,
|
|
&resourcemanager.GetProjectResponse{
|
|
ContainerId: utils.Ptr("cid"),
|
|
ProjectId: utils.Ptr("pid"),
|
|
Labels: &map[string]string{
|
|
"label1": "ref1",
|
|
"label2": "ref2",
|
|
},
|
|
Parent: &resourcemanager.Parent{
|
|
ContainerId: utils.Ptr("parent_cid"),
|
|
Id: utils.Ptr(testUUID),
|
|
},
|
|
Name: utils.Ptr("name"),
|
|
},
|
|
Model{
|
|
Id: types.StringValue("cid"),
|
|
ContainerId: types.StringValue("cid"),
|
|
ProjectId: types.StringValue("pid"),
|
|
ContainerParentId: types.StringValue(testUUID),
|
|
Name: types.StringValue("name"),
|
|
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
|
|
},
|
|
&map[string]string{
|
|
"label1": "ref1",
|
|
"label2": "ref2",
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"response_nil_fail",
|
|
false,
|
|
nil,
|
|
Model{},
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
"no_resource_id",
|
|
false,
|
|
&resourcemanager.GetProjectResponse{},
|
|
Model{},
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
if tt.expectedLabels == nil {
|
|
tt.expected.Labels = types.MapNull(types.StringType)
|
|
} else {
|
|
convertedLabels, err := conversion.ToTerraformStringMap(context.Background(), *tt.expectedLabels)
|
|
if err != nil {
|
|
t.Fatalf("Error converting to terraform string map: %v", err)
|
|
}
|
|
tt.expected.Labels = convertedLabels
|
|
}
|
|
var containerParentId = types.StringNull()
|
|
if tt.uuidContainerParentId {
|
|
containerParentId = types.StringValue(testUUID)
|
|
}
|
|
model := &Model{
|
|
ContainerId: tt.expected.ContainerId,
|
|
ContainerParentId: containerParentId,
|
|
Members: types.ListNull(types.ObjectType{AttrTypes: memberTypes}),
|
|
}
|
|
|
|
err := mapProjectFields(context.Background(), tt.projectResp, model, nil)
|
|
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(model, &tt.expected)
|
|
if diff != "" {
|
|
t.Fatalf("Data does not match: %s", diff)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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
|
|
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"),
|
|
},
|
|
),
|
|
}),
|
|
},
|
|
&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,
|
|
},
|
|
{
|
|
"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",
|
|
},
|
|
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,
|
|
false,
|
|
},
|
|
{
|
|
"nil_model",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
if tt.input != nil {
|
|
if tt.inputLabels == nil {
|
|
tt.input.Labels = types.MapNull(types.StringType)
|
|
} else {
|
|
convertedLabels, err := conversion.ToTerraformStringMap(context.Background(), *tt.inputLabels)
|
|
if err != nil {
|
|
t.Fatalf("Error converting to terraform string map: %v", err)
|
|
}
|
|
tt.input.Labels = convertedLabels
|
|
}
|
|
}
|
|
output, err := toCreatePayload(context.Background(), tt.input)
|
|
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
|
|
inputLabels *map[string]string
|
|
expected *resourcemanager.PartialUpdateProjectPayload
|
|
isValid bool
|
|
}{
|
|
{
|
|
"default_ok",
|
|
&Model{},
|
|
nil,
|
|
&resourcemanager.PartialUpdateProjectPayload{
|
|
ContainerParentId: nil,
|
|
Labels: nil,
|
|
Name: nil,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"mapping_with_conversions_ok",
|
|
&Model{
|
|
ContainerParentId: types.StringValue("pid"),
|
|
Name: types.StringValue("name"),
|
|
OwnerEmail: types.StringValue("owner_email"),
|
|
},
|
|
&map[string]string{
|
|
"label1": "1",
|
|
"label2": "2",
|
|
},
|
|
&resourcemanager.PartialUpdateProjectPayload{
|
|
ContainerParentId: utils.Ptr("pid"),
|
|
Labels: &map[string]string{
|
|
"label1": "1",
|
|
"label2": "2",
|
|
},
|
|
Name: utils.Ptr("name"),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"nil_model",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
if tt.input != nil {
|
|
if tt.inputLabels == nil {
|
|
tt.input.Labels = types.MapNull(types.StringType)
|
|
} else {
|
|
convertedLabels, err := conversion.ToTerraformStringMap(context.Background(), *tt.inputLabels)
|
|
if err != nil {
|
|
t.Fatalf("Error converting to terraform string map: %v", err)
|
|
}
|
|
tt.input.Labels = convertedLabels
|
|
}
|
|
}
|
|
output, err := toUpdatePayload(tt.input)
|
|
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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var fixtureMembers = []authorization.Member{
|
|
{
|
|
Subject: utils.Ptr("email_owner"),
|
|
Role: utils.Ptr("owner"),
|
|
},
|
|
{
|
|
Subject: utils.Ptr("email_owner_2"),
|
|
Role: utils.Ptr("owner"),
|
|
},
|
|
{
|
|
Subject: utils.Ptr("email_reader"),
|
|
Role: utils.Ptr("reader"),
|
|
},
|
|
}
|
|
|
|
func TestUpdateMembers(t *testing.T) {
|
|
// This is the response used when getting all members currently, across all tests
|
|
getAllMembersResp := authorization.MembersResponse{
|
|
Members: &fixtureMembers,
|
|
}
|
|
getAllMembersRespBytes, err := json.Marshal(getAllMembersResp)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal get all Members response: %v", err)
|
|
}
|
|
|
|
// This is the response used whenever an API returns a failure response
|
|
failureRespBytes := []byte("{\"message\": \"Something bad happened\"")
|
|
|
|
tests := []struct {
|
|
description string
|
|
modelMembers []authorization.Member
|
|
getAllMembersFails bool
|
|
addMembersFails bool
|
|
removeMembersFails bool
|
|
isValid bool
|
|
expectedMembersStates map[string]bool // Keys are member; value is true if member should exist at the end, false otherwise
|
|
}{
|
|
{
|
|
description: "no changes",
|
|
modelMembers: fixtureMembers,
|
|
expectedMembersStates: map[string]bool{
|
|
memberId(fixtureMembers[0]): true,
|
|
memberId(fixtureMembers[1]): true,
|
|
memberId(fixtureMembers[2]): true,
|
|
},
|
|
isValid: true,
|
|
},
|
|
{
|
|
description: "add one member",
|
|
modelMembers: append(
|
|
fixtureMembers,
|
|
authorization.Member{Subject: utils.Ptr("email_reader_2"), Role: utils.Ptr("reader")},
|
|
),
|
|
expectedMembersStates: map[string]bool{
|
|
memberId(fixtureMembers[0]): true,
|
|
memberId(fixtureMembers[1]): true,
|
|
memberId(fixtureMembers[2]): true,
|
|
"email_reader_2,reader": true,
|
|
},
|
|
isValid: true,
|
|
},
|
|
{
|
|
description: "add multiple members",
|
|
modelMembers: append(
|
|
fixtureMembers,
|
|
authorization.Member{Subject: utils.Ptr("email_reader_2"), Role: utils.Ptr("reader")},
|
|
authorization.Member{Subject: utils.Ptr("email_reader_3"), Role: utils.Ptr("reader")},
|
|
),
|
|
expectedMembersStates: map[string]bool{
|
|
memberId(fixtureMembers[0]): true,
|
|
memberId(fixtureMembers[1]): true,
|
|
memberId(fixtureMembers[2]): true,
|
|
"email_reader_2,reader": true,
|
|
"email_reader_3,reader": true,
|
|
},
|
|
isValid: true,
|
|
},
|
|
{
|
|
description: "removing member",
|
|
modelMembers: fixtureMembers[:2],
|
|
expectedMembersStates: map[string]bool{
|
|
memberId(fixtureMembers[0]): true,
|
|
memberId(fixtureMembers[1]): true,
|
|
memberId(fixtureMembers[2]): false,
|
|
},
|
|
isValid: true,
|
|
},
|
|
{
|
|
description: "removing multiple members",
|
|
modelMembers: fixtureMembers[:1],
|
|
expectedMembersStates: map[string]bool{
|
|
memberId(fixtureMembers[0]): true,
|
|
memberId(fixtureMembers[1]): false,
|
|
memberId(fixtureMembers[2]): false,
|
|
},
|
|
isValid: true,
|
|
},
|
|
{
|
|
description: "multiple changes (add and remove)",
|
|
modelMembers: append(
|
|
fixtureMembers[:2],
|
|
authorization.Member{Subject: utils.Ptr("email_reader_2"), Role: utils.Ptr("reader")},
|
|
authorization.Member{Subject: utils.Ptr("email_reader_3"), Role: utils.Ptr("reader")},
|
|
),
|
|
expectedMembersStates: map[string]bool{
|
|
memberId(fixtureMembers[0]): true,
|
|
memberId(fixtureMembers[1]): true,
|
|
memberId(fixtureMembers[2]): false,
|
|
"email_reader_2,reader": true,
|
|
"email_reader_3,reader": true,
|
|
},
|
|
isValid: true,
|
|
},
|
|
{
|
|
description: "multiple changes 2 (add and remove)",
|
|
modelMembers: []authorization.Member{
|
|
{Subject: utils.Ptr("email_reader_2"), Role: utils.Ptr("reader")},
|
|
{Subject: utils.Ptr("email_reader_3"), Role: utils.Ptr("reader")},
|
|
},
|
|
expectedMembersStates: map[string]bool{
|
|
memberId(fixtureMembers[0]): false,
|
|
memberId(fixtureMembers[1]): false,
|
|
memberId(fixtureMembers[2]): false,
|
|
"email_reader_2,reader": true,
|
|
"email_reader_3,reader": true,
|
|
},
|
|
isValid: true,
|
|
},
|
|
{
|
|
description: "get fails",
|
|
modelMembers: fixtureMembers,
|
|
getAllMembersFails: true,
|
|
isValid: false,
|
|
},
|
|
{
|
|
description: "add fails",
|
|
modelMembers: append(
|
|
fixtureMembers,
|
|
authorization.Member{Subject: utils.Ptr("email_reader_2"), Role: utils.Ptr("reader")},
|
|
),
|
|
addMembersFails: true,
|
|
isValid: false,
|
|
},
|
|
{
|
|
description: "remove fails",
|
|
modelMembers: fixtureMembers[:1],
|
|
removeMembersFails: true,
|
|
isValid: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
// Will be compared to tt.expectedMembersStates at the end
|
|
membersStates := map[string]bool{
|
|
memberId(fixtureMembers[0]): true,
|
|
memberId(fixtureMembers[1]): true,
|
|
memberId(fixtureMembers[2]): true,
|
|
}
|
|
|
|
// Handler for getting all Members
|
|
getAllMembersHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if tt.getAllMembersFails {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, err := w.Write(failureRespBytes)
|
|
if err != nil {
|
|
t.Errorf("Get all Members handler: failed to write bad response: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
_, err := w.Write(getAllMembersRespBytes)
|
|
if err != nil {
|
|
t.Errorf("Get all Members handler: failed to write response: %v", err)
|
|
}
|
|
})
|
|
|
|
// Handler for adding members
|
|
addMembersHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
decoder := json.NewDecoder(r.Body)
|
|
var payload authorization.AddMembersPayload
|
|
err := decoder.Decode(&payload)
|
|
if err != nil {
|
|
t.Errorf("Add members handler: failed to parse payload")
|
|
return
|
|
}
|
|
if payload.Members == nil {
|
|
t.Errorf("Add members handler: nil members")
|
|
return
|
|
}
|
|
members := *payload.Members
|
|
for _, m := range members {
|
|
if memberExists, memberWasAdded := membersStates[memberId(m)]; memberWasAdded && memberExists {
|
|
t.Errorf("Add members handler: attempted to add member '%v' that already exists", memberId(m))
|
|
return
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if tt.addMembersFails {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, err := w.Write(failureRespBytes)
|
|
if err != nil {
|
|
t.Errorf("Add members handler: failed to write bad response: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
for _, m := range members {
|
|
membersStates[memberId(m)] = true
|
|
}
|
|
})
|
|
|
|
// Handler for removing members
|
|
removeMembersHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
decoder := json.NewDecoder(r.Body)
|
|
var payload authorization.RemoveMembersPayload
|
|
err := decoder.Decode(&payload)
|
|
if err != nil {
|
|
t.Errorf("Remove members handler: failed to parse payload")
|
|
return
|
|
}
|
|
if payload.Members == nil {
|
|
t.Errorf("Remove members handler: nil members")
|
|
return
|
|
}
|
|
members := *payload.Members
|
|
for _, m := range members {
|
|
memberExists, memberWasCreated := membersStates[memberId(m)]
|
|
if !memberWasCreated {
|
|
t.Errorf("Remove members handler: attempted to remove member '%v' that wasn't created", memberId(m))
|
|
return
|
|
}
|
|
if memberWasCreated && !memberExists {
|
|
t.Errorf("Remove members handler: attempted to remove member '%v' that was already removed", memberId(m))
|
|
return
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if tt.removeMembersFails {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, err := w.Write(failureRespBytes)
|
|
if err != nil {
|
|
t.Errorf("Remove members handler: failed to write bad response: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
for _, m := range members {
|
|
membersStates[memberId(m)] = false
|
|
}
|
|
})
|
|
|
|
// Setup server and client
|
|
router := mux.NewRouter()
|
|
router.HandleFunc("/v2/{resourceType}/{resourceId}/members", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodGet {
|
|
getAllMembersHandler(w, r)
|
|
} else {
|
|
t.Fatalf("Unexpected method: %v", r.Method)
|
|
}
|
|
})
|
|
router.HandleFunc("/v2/{resourceId}/members", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPatch {
|
|
addMembersHandler(w, r)
|
|
} else {
|
|
t.Fatalf("Unexpected method: %v", r.Method)
|
|
}
|
|
})
|
|
router.HandleFunc("/v2/{resourceId}/members/remove", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPost {
|
|
removeMembersHandler(w, r)
|
|
} else {
|
|
t.Fatalf("Unexpected method: %v", r.Method)
|
|
}
|
|
})
|
|
mockedServer := httptest.NewServer(router)
|
|
defer mockedServer.Close()
|
|
client, err := authorization.NewAPIClient(
|
|
config.WithEndpoint(mockedServer.URL),
|
|
config.WithoutAuthentication(),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Failed to initialize client: %v", err)
|
|
}
|
|
|
|
// Run test
|
|
err = updateMembers(context.Background(), "pid", &tt.modelMembers, client)
|
|
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(membersStates, tt.expectedMembersStates)
|
|
if diff != "" {
|
|
t.Fatalf("Member states do not match: %s", diff)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|