From 7fe9198bce7ef849e9010e2541c2c704eda1bd28 Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Mon, 29 Jan 2024 13:56:25 -0800 Subject: [PATCH] testing/e2e: paginate managed groups (#4315) --- testing/internal/e2e/boundary/authmethod.go | 18 +- testing/internal/e2e/boundary/managedgroup.go | 27 +++ .../tests/base/paginate_managed_group_test.go | 206 ++++++++++++++++++ 3 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 testing/internal/e2e/boundary/managedgroup.go create mode 100644 testing/internal/e2e/tests/base/paginate_managed_group_test.go diff --git a/testing/internal/e2e/boundary/authmethod.go b/testing/internal/e2e/boundary/authmethod.go index edf143b5d1..497a00d4c4 100644 --- a/testing/internal/e2e/boundary/authmethod.go +++ b/testing/internal/e2e/boundary/authmethod.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -// CreateNewAuthMethodApi creates a new auth method using the Go api. +// CreateNewAuthMethodApi creates a new password auth method using the Go api. // Returns the id of the new auth method func CreateNewAuthMethodApi(t testing.TB, ctx context.Context, client *api.Client, scopeId string) string { aClient := authmethods.NewClient(client) @@ -23,3 +23,19 @@ func CreateNewAuthMethodApi(t testing.TB, ctx context.Context, client *api.Clien t.Logf("Created Auth Method: %s", authMethodId) return authMethodId } + +// CreateNewOidcAuthMethodApi creates a new oidc auth method using the Go api. +// Returns the id of the new auth method +func CreateNewOidcAuthMethodApi(t testing.TB, ctx context.Context, client *api.Client, scopeId string) string { + aClient := authmethods.NewClient(client) + newAMResult, err := aClient.Create(ctx, "oidc", scopeId, + authmethods.WithOidcAuthMethodApiUrlPrefix("https://some_url_prefix"), + authmethods.WithOidcAuthMethodClientId("some_client_id"), + authmethods.WithOidcAuthMethodClientSecret("some_client_secret"), + ) + require.NoError(t, err) + + authMethodId := newAMResult.Item.Id + t.Logf("Created Auth Method: %s", authMethodId) + return authMethodId +} diff --git a/testing/internal/e2e/boundary/managedgroup.go b/testing/internal/e2e/boundary/managedgroup.go new file mode 100644 index 0000000000..838bc7800f --- /dev/null +++ b/testing/internal/e2e/boundary/managedgroup.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package boundary + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/managedgroups" + "github.com/stretchr/testify/require" +) + +// CreateNewManagedGroupApi creates a new managed group using the Go api. +// Returns the id of the new managed group. +func CreateNewManagedGroupApi(t testing.TB, ctx context.Context, client *api.Client, amId string) string { + mgClient := managedgroups.NewClient(client) + newMGResult, err := mgClient.Create(ctx, amId, + managedgroups.WithOidcManagedGroupFilter(`"/token/zip" == "zap"`), + ) + require.NoError(t, err) + + managedGroupId := newMGResult.Item.Id + t.Logf("Created Managed Group: %s", managedGroupId) + return managedGroupId +} diff --git a/testing/internal/e2e/tests/base/paginate_managed_group_test.go b/testing/internal/e2e/tests/base/paginate_managed_group_test.go new file mode 100644 index 0000000000..a25dcf6fbf --- /dev/null +++ b/testing/internal/e2e/tests/base/paginate_managed_group_test.go @@ -0,0 +1,206 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package base_test + +import ( + "context" + "encoding/json" + "slices" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/api/authmethods" + "github.com/hashicorp/boundary/api/managedgroups" + "github.com/hashicorp/boundary/api/scopes" + "github.com/hashicorp/boundary/testing/internal/e2e" + "github.com/hashicorp/boundary/testing/internal/e2e/boundary" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCliPaginateManagedGroups asserts that the CLI automatically paginates to retrieve +// all managed groups in a single invocation. +func TestCliPaginateManagedGroups(t *testing.T) { + e2e.MaybeSkipTest(t) + c, err := loadTestConfig() + require.NoError(t, err) + + ctx := context.Background() + boundary.AuthenticateAdminCli(t, ctx) + client, err := boundary.NewApiClient() + require.NoError(t, err) + newOrgId := boundary.CreateNewOrgCli(t, ctx) + t.Cleanup(func() { + ctx := context.Background() + boundary.AuthenticateAdminCli(t, ctx) + output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", newOrgId)) + require.NoError(t, output.Err, string(output.Stderr)) + }) + amId := boundary.CreateNewOidcAuthMethodApi(t, ctx, client, newOrgId) + t.Cleanup(func() { + ctx := context.Background() + boundary.AuthenticateAdminCli(t, ctx) + output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("auth-methods", "delete", "-id", amId)) + require.NoError(t, output.Err, string(output.Stderr)) + }) + + // Create enough managedgroups to overflow a single page. + var managedgroupIds []string + for i := 0; i < c.MaxPageSize+1; i++ { + managedgroupIds = append(managedgroupIds, boundary.CreateNewManagedGroupApi(t, ctx, client, amId)) + } + + // List managedgroups + output := e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "managed-groups", "list", + "-auth-method-id", amId, + "-format=json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + var initialManagedGroups managedgroups.ManagedGroupListResult + err = json.Unmarshal(output.Stdout, &initialManagedGroups) + require.NoError(t, err) + + var returnedIds []string + for _, managedgroup := range initialManagedGroups.Items { + returnedIds = append(returnedIds, managedgroup.Id) + } + + require.Len(t, initialManagedGroups.Items, c.MaxPageSize+1) + assert.Empty(t, cmp.Diff(returnedIds, managedgroupIds, cmpopts.SortSlices(func(i, j string) bool { return i < j }))) + assert.Empty(t, initialManagedGroups.ResponseType) + assert.Empty(t, initialManagedGroups.RemovedIds) + assert.Empty(t, initialManagedGroups.ListToken) + + // Create a new managedgroup and destroy one of the other managed groups + newManagedGroupId := boundary.CreateNewManagedGroupApi(t, ctx, client, amId) + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "managed-groups", "delete", + "-id", initialManagedGroups.Items[0].Id, + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + // List again, should have the new managedgroup but not the deleted managed group + output = e2e.RunCommand(ctx, "boundary", + e2e.WithArgs( + "managed-groups", "list", + "-auth-method-id", amId, + "-format=json", + ), + ) + require.NoError(t, output.Err, string(output.Stderr)) + + var newManagedGroups managedgroups.ManagedGroupListResult + err = json.Unmarshal(output.Stdout, &newManagedGroups) + require.NoError(t, err) + + require.Len(t, newManagedGroups.Items, c.MaxPageSize+1) + // The first item should be the most recently created, which + // should be our new managedgroup + firstItem := newManagedGroups.Items[0] + assert.Equal(t, newManagedGroupId, firstItem.Id) + assert.Empty(t, newManagedGroups.ResponseType) + assert.Empty(t, newManagedGroups.RemovedIds) + assert.Empty(t, newManagedGroups.ListToken) + // Ensure the deleted managedgroup isn't returned + for _, managedgroup := range newManagedGroups.Items { + assert.NotEqual(t, managedgroup.Id, initialManagedGroups.Items[0].Id) + } +} + +// TestApiPaginateManagedGroups asserts that the API automatically paginates to retrieve +// all managedgroups in a single invocation. +func TestApiPaginateManagedGroups(t *testing.T) { + e2e.MaybeSkipTest(t) + c, err := loadTestConfig() + require.NoError(t, err) + + client, err := boundary.NewApiClient() + require.NoError(t, err) + adminToken := client.Token() + ctx := context.Background() + sClient := scopes.NewClient(client) + amClient := authmethods.NewClient(client) + mgClient := managedgroups.NewClient(client) + newOrgId := boundary.CreateNewOrgApi(t, ctx, client) + t.Cleanup(func() { + ctx := context.Background() + client.SetToken(adminToken) + _, err = sClient.Delete(ctx, newOrgId) + require.NoError(t, err) + }) + amId := boundary.CreateNewOidcAuthMethodApi(t, ctx, client, newOrgId) + t.Cleanup(func() { + ctx := context.Background() + client.SetToken(adminToken) + _, err := amClient.Delete(ctx, amId) + require.NoError(t, err) + }) + + // Create enough managedgroups to overflow a single page. + var managedgroupIds []string + for i := 0; i < c.MaxPageSize+1; i++ { + managedgroupIds = append(managedgroupIds, boundary.CreateNewManagedGroupApi(t, ctx, client, amId)) + } + + // List managedgroups + initialManagedGroups, err := mgClient.List(ctx, amId) + require.NoError(t, err) + + var returnedIds []string + for _, managedgroup := range initialManagedGroups.Items { + returnedIds = append(returnedIds, managedgroup.Id) + } + + require.Len(t, initialManagedGroups.Items, c.MaxPageSize+1) + assert.Empty(t, cmp.Diff(returnedIds, managedgroupIds, cmpopts.SortSlices(func(i, j string) bool { return i < j }))) + assert.Equal(t, "complete", initialManagedGroups.ResponseType) + assert.Empty(t, initialManagedGroups.RemovedIds) + assert.NotEmpty(t, initialManagedGroups.ListToken) + mapItems, ok := initialManagedGroups.GetResponse().Map["items"] + require.True(t, ok) + mapSliceItems, ok := mapItems.([]any) + require.True(t, ok) + assert.Len(t, mapSliceItems, c.MaxPageSize+1) + + // Create a new managedgroup and destroy one of the other managed groups + newManagedGroupId := boundary.CreateNewManagedGroupApi(t, ctx, client, amId) + _, err = mgClient.Delete(ctx, initialManagedGroups.Items[0].Id) + require.NoError(t, err) + + // List again, should have the new and deleted managedgroup + newManagedGroups, err := mgClient.List(ctx, amId, managedgroups.WithListToken(initialManagedGroups.ListToken)) + require.NoError(t, err) + + // Note that this will likely contain all the managed groups, + // since they were created very shortly before the listing, + // and we add a 30 second buffer to the lower bound of update + // times when listing. + require.GreaterOrEqual(t, len(newManagedGroups.Items), 1) + // The first item should be the most recently created, which + // should be our new managedgroup + firstItem := newManagedGroups.Items[0] + assert.Equal(t, newManagedGroupId, firstItem.Id) + assert.Equal(t, "complete", newManagedGroups.ResponseType) + // Note that the removed IDs may contain entries from other tests, + // so just check that there is at least 1 entry and that our entry + // is somewhere in the list. + require.GreaterOrEqual(t, len(newManagedGroups.RemovedIds), 1) + assert.True(t, slices.ContainsFunc(newManagedGroups.RemovedIds, func(managedgroupId string) bool { + return managedgroupId == initialManagedGroups.Items[0].Id + })) + assert.NotEmpty(t, newManagedGroups.ListToken) + // Check that the response map contains all entries + mapItems, ok = newManagedGroups.GetResponse().Map["items"] + require.True(t, ok) + mapSliceItems, ok = mapItems.([]any) + require.True(t, ok) + assert.GreaterOrEqual(t, len(mapSliceItems), 1) +}