Skip to content

Commit

Permalink
chore: test creating a group with unmanaged members (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanndickson authored Jul 30, 2024
1 parent bcba9b7 commit 9b0c900
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 71 deletions.
46 changes: 16 additions & 30 deletions internal/provider/group_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,19 +240,24 @@ func (r *GroupResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group, got error: %s", err))
return
}
var newMembers []string
resp.Diagnostics.Append(
data.Members.ElementsAs(ctx, &newMembers, false)...,
)
if resp.Diagnostics.HasError() {
return
}
var add []string
var remove []string
if !data.Members.IsNull() {
add, remove = memberDiff(group.Members, newMembers)
var plannedMembers []UUID
resp.Diagnostics.Append(
data.Members.ElementsAs(ctx, &plannedMembers, false)...,
)
if resp.Diagnostics.HasError() {
return
}
curMembers := make([]uuid.UUID, 0, len(group.Members))
for _, member := range group.Members {
curMembers = append(curMembers, member.ID)
}
add, remove = memberDiff(curMembers, plannedMembers)
}
tflog.Trace(ctx, "updating group", map[string]any{
"id": groupID,
"new_members": add,
"removed_members": remove,
"new_name": data.Name,
Expand Down Expand Up @@ -293,7 +298,9 @@ func (r *GroupResource) Delete(ctx context.Context, req resource.DeleteRequest,
client := r.data.Client
groupID := data.ID.ValueUUID()

tflog.Trace(ctx, "deleting group")
tflog.Trace(ctx, "deleting group", map[string]any{
"id": groupID,
})
err := client.DeleteGroup(ctx, groupID)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete group, got error: %s", err))
Expand All @@ -320,24 +327,3 @@ func (r *GroupResource) ImportState(ctx context.Context, req resource.ImportStat
}
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

func memberDiff(curMembers []codersdk.ReducedUser, newMembers []string) (add, remove []string) {
curSet := make(map[string]struct{}, len(curMembers))
newSet := make(map[string]struct{}, len(newMembers))

for _, user := range curMembers {
curSet[user.ID.String()] = struct{}{}
}
for _, userID := range newMembers {
newSet[userID] = struct{}{}
if _, exists := curSet[userID]; !exists {
add = append(add, userID)
}
}
for _, user := range curMembers {
if _, exists := newSet[user.ID.String()]; !exists {
remove = append(remove, user.ID.String())
}
}
return add, remove
}
99 changes: 58 additions & 41 deletions internal/provider/group_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,49 +62,66 @@ func TestAccGroupResource(t *testing.T) {
cfg3 := cfg2
cfg3.Members = nil

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read
{
Config: cfg1.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group"),
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group"),
resource.TestCheckResourceAttr("coderd_group.test", "avatar_url", "https://google.com"),
resource.TestCheckResourceAttr("coderd_group.test", "quota_allowance", "100"),
resource.TestCheckResourceAttr("coderd_group.test", "organization_id", firstUser.OrganizationIDs[0].String()),
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user1.ID.String()),
),
t.Run("CreateImportUpdateReadOk", func(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read
{
Config: cfg1.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group"),
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group"),
resource.TestCheckResourceAttr("coderd_group.test", "avatar_url", "https://google.com"),
resource.TestCheckResourceAttr("coderd_group.test", "quota_allowance", "100"),
resource.TestCheckResourceAttr("coderd_group.test", "organization_id", firstUser.OrganizationIDs[0].String()),
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user1.ID.String()),
),
},
// Import
{
Config: cfg1.String(t),
ResourceName: "coderd_group.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"members"},
},
// Update and Read
{
Config: cfg2.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group-new"),
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group New"),
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user2.ID.String()),
),
},
// Unmanaged members
{
Config: cfg3.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckNoResourceAttr("coderd_group.test", "members"),
),
},
},
// Import
{
Config: cfg1.String(t),
ResourceName: "coderd_group.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"members"},
},
// Update and Read
{
Config: cfg2.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group-new"),
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group New"),
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user2.ID.String()),
),
},
// Unmanaged members
{
Config: cfg3.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckNoResourceAttr("coderd_group.test", "members"),
),
})
})

t.Run("CreateUnmanagedMembersOk", func(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: cfg3.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckNoResourceAttr("coderd_group.test", "members"),
),
},
},
},
})
})
}

Expand Down
25 changes: 25 additions & 0 deletions internal/provider/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"os"
"path/filepath"

"github.com/google/uuid"
)

func PtrTo[T any](v T) *T {
Expand Down Expand Up @@ -76,3 +78,26 @@ func computeDirectoryHash(directory string) (string, error) {
}
return hex.EncodeToString(hash.Sum(nil)), nil
}

// memberDiff returns the members to add and remove from the group, given the current members and the planned members.
// plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set.
func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) {
curSet := make(map[uuid.UUID]struct{}, len(curMembers))
planSet := make(map[uuid.UUID]struct{}, len(plannedMembers))

for _, userID := range curMembers {
curSet[userID] = struct{}{}
}
for _, plannedUserID := range plannedMembers {
planSet[plannedUserID.ValueUUID()] = struct{}{}
if _, exists := curSet[plannedUserID.ValueUUID()]; !exists {
add = append(add, plannedUserID.ValueString())
}
}
for _, curUserID := range curMembers {
if _, exists := planSet[curUserID]; !exists {
remove = append(remove, curUserID.String())
}
}
return add, remove
}

0 comments on commit 9b0c900

Please sign in to comment.