Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9b0c900

Browse files
chore: test creating a group with unmanaged members (#48)
1 parent bcba9b7 commit 9b0c900

File tree

3 files changed

+99
-71
lines changed

3 files changed

+99
-71
lines changed

internal/provider/group_resource.go

+16-30
Original file line numberDiff line numberDiff line change
@@ -240,19 +240,24 @@ func (r *GroupResource) Update(ctx context.Context, req resource.UpdateRequest,
240240
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group, got error: %s", err))
241241
return
242242
}
243-
var newMembers []string
244-
resp.Diagnostics.Append(
245-
data.Members.ElementsAs(ctx, &newMembers, false)...,
246-
)
247-
if resp.Diagnostics.HasError() {
248-
return
249-
}
250243
var add []string
251244
var remove []string
252245
if !data.Members.IsNull() {
253-
add, remove = memberDiff(group.Members, newMembers)
246+
var plannedMembers []UUID
247+
resp.Diagnostics.Append(
248+
data.Members.ElementsAs(ctx, &plannedMembers, false)...,
249+
)
250+
if resp.Diagnostics.HasError() {
251+
return
252+
}
253+
curMembers := make([]uuid.UUID, 0, len(group.Members))
254+
for _, member := range group.Members {
255+
curMembers = append(curMembers, member.ID)
256+
}
257+
add, remove = memberDiff(curMembers, plannedMembers)
254258
}
255259
tflog.Trace(ctx, "updating group", map[string]any{
260+
"id": groupID,
256261
"new_members": add,
257262
"removed_members": remove,
258263
"new_name": data.Name,
@@ -293,7 +298,9 @@ func (r *GroupResource) Delete(ctx context.Context, req resource.DeleteRequest,
293298
client := r.data.Client
294299
groupID := data.ID.ValueUUID()
295300

296-
tflog.Trace(ctx, "deleting group")
301+
tflog.Trace(ctx, "deleting group", map[string]any{
302+
"id": groupID,
303+
})
297304
err := client.DeleteGroup(ctx, groupID)
298305
if err != nil {
299306
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete group, got error: %s", err))
@@ -320,24 +327,3 @@ func (r *GroupResource) ImportState(ctx context.Context, req resource.ImportStat
320327
}
321328
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
322329
}
323-
324-
func memberDiff(curMembers []codersdk.ReducedUser, newMembers []string) (add, remove []string) {
325-
curSet := make(map[string]struct{}, len(curMembers))
326-
newSet := make(map[string]struct{}, len(newMembers))
327-
328-
for _, user := range curMembers {
329-
curSet[user.ID.String()] = struct{}{}
330-
}
331-
for _, userID := range newMembers {
332-
newSet[userID] = struct{}{}
333-
if _, exists := curSet[userID]; !exists {
334-
add = append(add, userID)
335-
}
336-
}
337-
for _, user := range curMembers {
338-
if _, exists := newSet[user.ID.String()]; !exists {
339-
remove = append(remove, user.ID.String())
340-
}
341-
}
342-
return add, remove
343-
}

internal/provider/group_resource_test.go

+58-41
Original file line numberDiff line numberDiff line change
@@ -62,49 +62,66 @@ func TestAccGroupResource(t *testing.T) {
6262
cfg3 := cfg2
6363
cfg3.Members = nil
6464

65-
resource.Test(t, resource.TestCase{
66-
PreCheck: func() { testAccPreCheck(t) },
67-
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
68-
Steps: []resource.TestStep{
69-
// Create and Read
70-
{
71-
Config: cfg1.String(t),
72-
Check: resource.ComposeAggregateTestCheckFunc(
73-
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group"),
74-
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group"),
75-
resource.TestCheckResourceAttr("coderd_group.test", "avatar_url", "https://google.com"),
76-
resource.TestCheckResourceAttr("coderd_group.test", "quota_allowance", "100"),
77-
resource.TestCheckResourceAttr("coderd_group.test", "organization_id", firstUser.OrganizationIDs[0].String()),
78-
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
79-
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user1.ID.String()),
80-
),
65+
t.Run("CreateImportUpdateReadOk", func(t *testing.T) {
66+
resource.Test(t, resource.TestCase{
67+
PreCheck: func() { testAccPreCheck(t) },
68+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
69+
Steps: []resource.TestStep{
70+
// Create and Read
71+
{
72+
Config: cfg1.String(t),
73+
Check: resource.ComposeAggregateTestCheckFunc(
74+
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group"),
75+
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group"),
76+
resource.TestCheckResourceAttr("coderd_group.test", "avatar_url", "https://google.com"),
77+
resource.TestCheckResourceAttr("coderd_group.test", "quota_allowance", "100"),
78+
resource.TestCheckResourceAttr("coderd_group.test", "organization_id", firstUser.OrganizationIDs[0].String()),
79+
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
80+
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user1.ID.String()),
81+
),
82+
},
83+
// Import
84+
{
85+
Config: cfg1.String(t),
86+
ResourceName: "coderd_group.test",
87+
ImportState: true,
88+
ImportStateVerify: true,
89+
ImportStateVerifyIgnore: []string{"members"},
90+
},
91+
// Update and Read
92+
{
93+
Config: cfg2.String(t),
94+
Check: resource.ComposeAggregateTestCheckFunc(
95+
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group-new"),
96+
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group New"),
97+
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
98+
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user2.ID.String()),
99+
),
100+
},
101+
// Unmanaged members
102+
{
103+
Config: cfg3.String(t),
104+
Check: resource.ComposeAggregateTestCheckFunc(
105+
resource.TestCheckNoResourceAttr("coderd_group.test", "members"),
106+
),
107+
},
81108
},
82-
// Import
83-
{
84-
Config: cfg1.String(t),
85-
ResourceName: "coderd_group.test",
86-
ImportState: true,
87-
ImportStateVerify: true,
88-
ImportStateVerifyIgnore: []string{"members"},
89-
},
90-
// Update and Read
91-
{
92-
Config: cfg2.String(t),
93-
Check: resource.ComposeAggregateTestCheckFunc(
94-
resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group-new"),
95-
resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group New"),
96-
resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"),
97-
resource.TestCheckResourceAttr("coderd_group.test", "members.0", user2.ID.String()),
98-
),
99-
},
100-
// Unmanaged members
101-
{
102-
Config: cfg3.String(t),
103-
Check: resource.ComposeAggregateTestCheckFunc(
104-
resource.TestCheckNoResourceAttr("coderd_group.test", "members"),
105-
),
109+
})
110+
})
111+
112+
t.Run("CreateUnmanagedMembersOk", func(t *testing.T) {
113+
resource.Test(t, resource.TestCase{
114+
PreCheck: func() { testAccPreCheck(t) },
115+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
116+
Steps: []resource.TestStep{
117+
{
118+
Config: cfg3.String(t),
119+
Check: resource.ComposeAggregateTestCheckFunc(
120+
resource.TestCheckNoResourceAttr("coderd_group.test", "members"),
121+
),
122+
},
106123
},
107-
},
124+
})
108125
})
109126
}
110127

internal/provider/util.go

+25
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77
"os"
88
"path/filepath"
9+
10+
"github.com/google/uuid"
911
)
1012

1113
func PtrTo[T any](v T) *T {
@@ -76,3 +78,26 @@ func computeDirectoryHash(directory string) (string, error) {
7678
}
7779
return hex.EncodeToString(hash.Sum(nil)), nil
7880
}
81+
82+
// memberDiff returns the members to add and remove from the group, given the current members and the planned members.
83+
// plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set.
84+
func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) {
85+
curSet := make(map[uuid.UUID]struct{}, len(curMembers))
86+
planSet := make(map[uuid.UUID]struct{}, len(plannedMembers))
87+
88+
for _, userID := range curMembers {
89+
curSet[userID] = struct{}{}
90+
}
91+
for _, plannedUserID := range plannedMembers {
92+
planSet[plannedUserID.ValueUUID()] = struct{}{}
93+
if _, exists := curSet[plannedUserID.ValueUUID()]; !exists {
94+
add = append(add, plannedUserID.ValueString())
95+
}
96+
}
97+
for _, curUserID := range curMembers {
98+
if _, exists := planSet[curUserID]; !exists {
99+
remove = append(remove, curUserID.String())
100+
}
101+
}
102+
return add, remove
103+
}

0 commit comments

Comments
 (0)