mirror of
https://github.com/gravitational/teleport.git
synced 2026-08-29 05:27:37 +08:00
642 lines
21 KiB
Go
642 lines
21 KiB
Go
// Teleport
|
|
// Copyright (C) 2025 Gravitational, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
|
|
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/api/types/accesslist"
|
|
"github.com/gravitational/teleport/api/types/header"
|
|
"github.com/gravitational/teleport/api/utils/clientutils"
|
|
"github.com/gravitational/teleport/lib/itertools/stream"
|
|
)
|
|
|
|
// TestAccessList tests that CRUD operations on access list resources are
|
|
// replicated from the backend to the cache.
|
|
func TestAccessList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
clock := clockwork.NewFakeClock()
|
|
|
|
testResources(t, p, testFuncs[*accesslist.AccessList]{
|
|
newResource: func(name string) (*accesslist.AccessList, error) {
|
|
return newAccessList(t, name, clock), nil
|
|
},
|
|
create: func(ctx context.Context, item *accesslist.AccessList) error {
|
|
_, err := p.accessLists.UpsertAccessList(ctx, item)
|
|
return trace.Wrap(err)
|
|
},
|
|
list: p.accessLists.ListAccessLists,
|
|
cacheGet: p.cache.GetAccessList,
|
|
cacheList: p.cache.ListAccessLists,
|
|
update: func(ctx context.Context, item *accesslist.AccessList) error {
|
|
_, err := p.accessLists.UpsertAccessList(ctx, item)
|
|
return trace.Wrap(err)
|
|
},
|
|
deleteAll: p.accessLists.DeleteAllAccessLists,
|
|
})
|
|
}
|
|
|
|
// TestAccessListMembers tests that CRUD operations on access list member resources are
|
|
// replicated from the backend to the cache.
|
|
func TestAccessListMembers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const numMembers = 32
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
clock := clockwork.NewFakeClock()
|
|
|
|
al, err := p.accessLists.UpsertAccessList(context.Background(), newAccessList(t, "access-list", clock))
|
|
require.NoError(t, err)
|
|
|
|
testResources(t, p, testFuncs[*accesslist.AccessListMember]{
|
|
newResource: func(name string) (*accesslist.AccessListMember, error) {
|
|
return newAccessListMember(t, al.GetName(), name), nil
|
|
},
|
|
create: func(ctx context.Context, item *accesslist.AccessListMember) error {
|
|
_, err := p.accessLists.UpsertAccessListMember(ctx, item)
|
|
return trace.Wrap(err)
|
|
},
|
|
list: p.accessLists.ListAllAccessListMembers,
|
|
cacheGet: func(ctx context.Context, name string) (*accesslist.AccessListMember, error) {
|
|
return p.cache.GetAccessListMember(ctx, al.GetName(), name)
|
|
},
|
|
cacheList: func(ctx context.Context, pageSize int, startKey string) ([]*accesslist.AccessListMember, string, error) {
|
|
return p.cache.ListAccessListMembers(ctx, al.GetName(), pageSize, startKey)
|
|
|
|
},
|
|
update: func(ctx context.Context, item *accesslist.AccessListMember) error {
|
|
_, err := p.accessLists.UpsertAccessListMember(ctx, item)
|
|
return trace.Wrap(err)
|
|
},
|
|
deleteAll: p.accessLists.DeleteAllAccessListMembers,
|
|
})
|
|
|
|
// Verify counting.
|
|
ctx := context.Background()
|
|
for i := range numMembers {
|
|
_, err = p.accessLists.UpsertAccessListMember(ctx, newAccessListMember(t, al.GetName(), strconv.Itoa(i)))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
count, listCount, err := p.accessLists.CountAccessListMembers(ctx, al.GetName())
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint32(numMembers), count)
|
|
require.Equal(t, uint32(0), listCount)
|
|
|
|
// Eventually, this should be reflected in the cache.
|
|
timeout := time.After(5 * time.Second)
|
|
for {
|
|
count, listCount, err := p.cache.CountAccessListMembers(ctx, al.GetName())
|
|
require.NoError(t, err)
|
|
if count == numMembers && listCount == 0 {
|
|
break
|
|
}
|
|
select {
|
|
case <-timeout:
|
|
require.Fail(t, "timed out waiting for correct member counts")
|
|
case <-p.eventsC:
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAccessListMembers_suffixed_lists_members_bug(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := t.Context()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
clock := clockwork.NewFakeClock()
|
|
|
|
al1, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, "test-1", clock))
|
|
require.NoError(t, err)
|
|
al1Suffix, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, al1.GetName()+"-suffix", clock))
|
|
require.NoError(t, err)
|
|
|
|
al1SuffixM1, err := p.accessLists.UpsertAccessListMember(ctx, newAccessListMember(t, al1Suffix.GetName(), "member-1"))
|
|
require.NoError(t, err)
|
|
|
|
// Eventually, this should be reflected in the cache.
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
al1SuffixMembers, _, err := p.cache.ListAccessListMembers(ctx, al1Suffix.GetName(), 0, "")
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, al1SuffixMembers)
|
|
}, time.Second*2, time.Millisecond*250)
|
|
|
|
al1Members, _, err := p.cache.ListAccessListMembers(ctx, al1.GetName(), 0, "")
|
|
require.NoError(t, err)
|
|
al1SuffixMembers, _, err := p.cache.ListAccessListMembers(ctx, al1Suffix.GetName(), 0, "")
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, al1Members)
|
|
require.Len(t, al1SuffixMembers, 1)
|
|
require.Equal(t, al1SuffixMembers[0].GetName(), al1SuffixM1.GetName())
|
|
}
|
|
|
|
func TestAccessListMembers_pageToken_compatibility(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := t.Context()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
clock := clockwork.NewFakeClock()
|
|
|
|
al1, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, "test-1", clock))
|
|
require.NoError(t, err)
|
|
|
|
const membersCnt = 50
|
|
|
|
for i := range membersCnt {
|
|
_, err := p.accessLists.UpsertAccessListMember(ctx, newAccessListMember(t, al1.GetName(), "member-"+strconv.Itoa(i)))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Eventually, this should be reflected in the cache.
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
al1Members, _, err := p.cache.ListAccessListMembers(ctx, al1.GetName(), 0, "")
|
|
require.NoError(t, err)
|
|
require.Len(t, al1Members, membersCnt)
|
|
}, time.Second*2, time.Millisecond*250)
|
|
|
|
const pageSize = 2
|
|
calls := 0
|
|
var localNextToken, cacheNextToken string
|
|
for range membersCnt * 2 {
|
|
calls++
|
|
|
|
_, localNextToken, err = p.accessLists.ListAccessListMembers(ctx, al1.GetName(), pageSize, localNextToken)
|
|
require.NoError(t, err)
|
|
_, cacheNextToken, err = p.cache.ListAccessListMembers(ctx, al1.GetName(), pageSize, cacheNextToken)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, localNextToken, cacheNextToken)
|
|
|
|
if localNextToken == "" {
|
|
break
|
|
}
|
|
}
|
|
require.Equal(t, membersCnt/pageSize, calls)
|
|
}
|
|
|
|
// TestAccessListReviews tests that CRUD operations on access list review resources are
|
|
// replicated from the backend to the cache.
|
|
func TestAccessListReviews(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
clock := clockwork.NewFakeClock()
|
|
|
|
al, _, err := p.accessLists.UpsertAccessListWithMembers(context.Background(), newAccessList(t, "access-list", clock),
|
|
[]*accesslist.AccessListMember{
|
|
newAccessListMember(t, "access-list", "member1"),
|
|
newAccessListMember(t, "access-list", "member2"),
|
|
newAccessListMember(t, "access-list", "member3"),
|
|
newAccessListMember(t, "access-list", "member4"),
|
|
newAccessListMember(t, "access-list", "member5"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Keep track of the reviews, as create can update them. We'll use this
|
|
// to make sure the values are up to date during the test.
|
|
reviews := map[string]*accesslist.Review{}
|
|
|
|
testResources(t, p, testFuncs[*accesslist.Review]{
|
|
newResource: func(name string) (*accesslist.Review, error) {
|
|
review := newAccessListReview(t, al.GetName(), name)
|
|
// Store the name in the description.
|
|
review.Metadata.Description = name
|
|
reviews[name] = review
|
|
return review, nil
|
|
},
|
|
create: func(ctx context.Context, item *accesslist.Review) error {
|
|
review, _, err := p.accessLists.CreateAccessListReview(ctx, item)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// Use the old name from the description.
|
|
oldName := review.Metadata.Description
|
|
reviews[oldName].SetName(review.GetName())
|
|
return trace.Wrap(err)
|
|
},
|
|
list: p.accessLists.ListAllAccessListReviews,
|
|
cacheList: func(ctx context.Context, pageSize int, startKey string) ([]*accesslist.Review, string, error) {
|
|
return p.cache.ListAccessListReviews(ctx, al.GetName(), pageSize, startKey)
|
|
},
|
|
deleteAll: p.accessLists.DeleteAllAccessListReviews,
|
|
}, withSkipPaginationTest()) // access list reviews resources have customer pagination test.
|
|
|
|
_, _, err = p.accessLists.UpsertAccessListWithMembers(t.Context(), newAccessList(t, "fake-al-1", clock),
|
|
[]*accesslist.AccessListMember{
|
|
newAccessListMember(t, "fake-al-1", "member1"),
|
|
newAccessListMember(t, "fake-al-1", "member2"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = p.accessLists.UpsertAccessListWithMembers(t.Context(), newAccessList(t, "fake-al-2", clock),
|
|
[]*accesslist.AccessListMember{
|
|
newAccessListMember(t, "fake-al-2", "member1"),
|
|
newAccessListMember(t, "fake-al-2", "member2"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
review1 := newAccessListReview(t, "fake-al-1", "initial-review-1")
|
|
review1, _, err = p.accessLists.CreateAccessListReview(t.Context(), review1)
|
|
require.NoError(t, err)
|
|
review2 := newAccessListReview(t, "fake-al-2", "initial-review-2")
|
|
review2, _, err = p.accessLists.CreateAccessListReview(t.Context(), review2)
|
|
require.NoError(t, err)
|
|
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
out, next, err := p.cache.ListAccessListReviews(context.Background(), "fake-al-1", 100, "")
|
|
require.NoError(t, err)
|
|
require.Empty(t, next)
|
|
|
|
require.Len(t, out, 1)
|
|
require.Empty(t, cmp.Diff([]*accesslist.Review{review1}, out,
|
|
cmpopts.IgnoreFields(header.Metadata{}, "Revision"),
|
|
protocmp.Transform()),
|
|
)
|
|
}, 15*time.Second, 100*time.Millisecond)
|
|
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
out, next, err := p.cache.ListAccessListReviews(context.Background(), "fake-al-2", 100, "")
|
|
require.NoError(t, err)
|
|
require.Empty(t, next)
|
|
|
|
require.Len(t, out, 1)
|
|
require.Empty(t, cmp.Diff([]*accesslist.Review{review2}, out,
|
|
cmpopts.IgnoreFields(header.Metadata{}, "Revision"),
|
|
protocmp.Transform()),
|
|
)
|
|
}, 15*time.Second, 100*time.Millisecond)
|
|
|
|
_, _, err = p.accessLists.UpsertAccessListWithMembers(t.Context(), newAccessList(t, "access-list-test", clock),
|
|
[]*accesslist.AccessListMember{
|
|
newAccessListMember(t, "access-list-test", "member1"),
|
|
newAccessListMember(t, "access-list-test", "member2"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
for i := range 10 {
|
|
review := newAccessListReview(t, "access-list-test", "fake-review-"+strconv.Itoa(i))
|
|
review.Spec.Changes = accesslist.ReviewChanges{}
|
|
_, _, err = p.accessLists.CreateAccessListReview(t.Context(), review)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
count := p.cache.collections.accessListReviews.store.len()
|
|
_ = count
|
|
|
|
var start string
|
|
var out []*accesslist.Review
|
|
for range 10 {
|
|
page, next, err := p.cache.ListAccessListReviews(context.Background(), "access-list-test", 3, start)
|
|
require.NoError(t, err)
|
|
|
|
out = append(out, page...)
|
|
if next == "" {
|
|
break
|
|
}
|
|
start = next
|
|
}
|
|
require.Len(t, out, 10)
|
|
}, 15*time.Second, 100*time.Millisecond)
|
|
|
|
// access-list is a prefix of access-list-test, make sure no reviews are
|
|
// returned from the wrong list.
|
|
out, _, err := p.cache.ListAccessListReviews(context.Background(), "access-list", 100, "")
|
|
require.NoError(t, err)
|
|
assert.Empty(t, out)
|
|
}
|
|
|
|
func TestGetAccessListOwners(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
clock := clockwork.NewFakeClock()
|
|
ctx := t.Context()
|
|
|
|
t.Run("owners directly from access list", func(t *testing.T) {
|
|
al, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, "owners-test-list", clock))
|
|
require.NoError(t, err)
|
|
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
owners, err := p.cache.GetAccessListOwners(ctx, al.GetName())
|
|
assert.NoError(t, err)
|
|
|
|
names := make([]string, 0, len(owners))
|
|
for _, o := range owners {
|
|
names = append(names, o.Name)
|
|
}
|
|
|
|
assert.ElementsMatch(t, []string{"test-user1", "test-user2"}, names)
|
|
}, 15*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("owners inherited from nested list", func(t *testing.T) {
|
|
nestedList, err := accesslist.NewAccessList(
|
|
header.Metadata{Name: "nested-owner-list"},
|
|
accesslist.Spec{
|
|
Title: "nested",
|
|
Owners: []accesslist.Owner{
|
|
{Name: "nested-list-owner", MembershipKind: accesslist.MembershipKindUser},
|
|
},
|
|
Audit: accesslist.Audit{NextAuditDate: clock.Now()},
|
|
Grants: accesslist.Grants{Roles: []string{"grant-role"}},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
nestedList, err = p.accessLists.UpsertAccessList(ctx, nestedList)
|
|
require.NoError(t, err)
|
|
|
|
_, err = p.accessLists.UpsertAccessListMember(ctx, newAccessListMember(t, nestedList.GetName(), "nested-member-1"))
|
|
require.NoError(t, err)
|
|
|
|
_, err = p.accessLists.UpsertAccessListMember(ctx, newAccessListMember(t, nestedList.GetName(), "nested-member-2"))
|
|
require.NoError(t, err)
|
|
|
|
parentList, err := accesslist.NewAccessList(
|
|
header.Metadata{Name: "parent-list"},
|
|
accesslist.Spec{
|
|
Title: "parent",
|
|
Owners: []accesslist.Owner{
|
|
{Name: nestedList.GetName(), MembershipKind: accesslist.MembershipKindList},
|
|
},
|
|
Audit: accesslist.Audit{NextAuditDate: clock.Now()},
|
|
Grants: accesslist.Grants{Roles: []string{"grant-role"}},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
parentList, err = p.accessLists.UpsertAccessList(ctx, parentList)
|
|
require.NoError(t, err)
|
|
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
owners, err := p.cache.GetAccessListOwners(ctx, parentList.GetName())
|
|
assert.NoError(t, err)
|
|
|
|
names := make([]string, 0, len(owners))
|
|
for _, o := range owners {
|
|
names = append(names, o.Name)
|
|
}
|
|
|
|
assert.ElementsMatch(t, []string{"nested-member-1", "nested-member-2"}, names)
|
|
}, 15*time.Second, 100*time.Millisecond)
|
|
})
|
|
}
|
|
|
|
func TestListAccessListsV2(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
ctx := context.Background()
|
|
baseTime := time.Date(1984, 4, 4, 0, 0, 0, 0, time.UTC)
|
|
clock := clockwork.NewFakeClockAt(baseTime)
|
|
|
|
names := []string{"apple-list", "banana-access", "cherry-management", "apple-admin", "zebra-test"}
|
|
|
|
for i, name := range names {
|
|
al := newAccessList(t, name, clock)
|
|
auditDate := clock.Now().Add(time.Duration(i) * (time.Hour) * 24)
|
|
// add arbitrary date so we can make sure its not just sorted by name
|
|
if name == "banana-access" {
|
|
auditDate = clock.Now().Add(100 * (time.Hour) * 24)
|
|
al.Spec.Title = "bananatitle"
|
|
}
|
|
al.Spec.Audit.NextAuditDate = auditDate
|
|
|
|
_, err := p.accessLists.UpsertAccessList(ctx, al)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
search string
|
|
sortBy *types.SortBy
|
|
expectedNames []string
|
|
pageSize int
|
|
expectedNextKey string
|
|
startKey string
|
|
}{
|
|
{
|
|
name: "no filter - all lists",
|
|
expectedNames: []string{"apple-admin", "apple-list", "banana-access", "cherry-management", "zebra-test"},
|
|
},
|
|
{
|
|
name: "sort by name reverse",
|
|
sortBy: &types.SortBy{Field: "name", IsDesc: true},
|
|
expectedNames: []string{"zebra-test", "cherry-management", "banana-access", "apple-list", "apple-admin"},
|
|
},
|
|
{
|
|
name: "sort by audit date",
|
|
sortBy: &types.SortBy{Field: "auditNextDate", IsDesc: false},
|
|
expectedNames: []string{"apple-list", "cherry-management", "apple-admin", "zebra-test", "banana-access"},
|
|
},
|
|
{
|
|
name: "sort by title",
|
|
sortBy: &types.SortBy{Field: "title", IsDesc: false},
|
|
expectedNames: []string{"banana-access", "apple-admin", "apple-list", "cherry-management", "zebra-test"},
|
|
},
|
|
{
|
|
name: "sort by title reverse",
|
|
sortBy: &types.SortBy{Field: "title", IsDesc: true},
|
|
expectedNames: []string{"zebra-test", "cherry-management", "apple-list", "apple-admin", "banana-access"},
|
|
},
|
|
{
|
|
name: "sort by audit date reverse",
|
|
sortBy: &types.SortBy{Field: "auditNextDate", IsDesc: true},
|
|
expectedNames: []string{"banana-access", "zebra-test", "apple-admin", "cherry-management", "apple-list"},
|
|
},
|
|
{
|
|
name: "paginated results",
|
|
expectedNames: []string{"apple-admin", "apple-list"},
|
|
pageSize: 2,
|
|
expectedNextKey: "banana-access",
|
|
},
|
|
{
|
|
name: "paginated results reverse",
|
|
expectedNames: []string{"zebra-test", "cherry-management", "banana-access"},
|
|
sortBy: &types.SortBy{Field: "name", IsDesc: true},
|
|
pageSize: 3,
|
|
expectedNextKey: "apple-list",
|
|
},
|
|
{
|
|
name: "with search",
|
|
search: "apple",
|
|
expectedNames: []string{"apple-admin", "apple-list"},
|
|
},
|
|
{
|
|
name: "with startKey",
|
|
startKey: "banana-access",
|
|
expectedNames: []string{"banana-access", "cherry-management", "zebra-test"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
results, nextToken, err := p.cache.ListAccessListsV2(ctx, accesslistv1.ListAccessListsV2Request_builder{
|
|
PageSize: int32(tc.pageSize),
|
|
PageToken: tc.startKey,
|
|
Filter: accesslistv1.AccessListsFilter_builder{
|
|
Search: tc.search,
|
|
}.Build(),
|
|
SortBy: tc.sortBy,
|
|
}.Build())
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedNextKey, nextToken)
|
|
|
|
require.Len(t, results, len(tc.expectedNames))
|
|
actualNames := make([]string, len(results))
|
|
for i, al := range results {
|
|
actualNames[i] = al.GetName()
|
|
}
|
|
|
|
require.Equal(t, tc.expectedNames, actualNames)
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCountAccessListMembersScoping(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
ctx := context.Background()
|
|
clock := clockwork.NewFakeClock()
|
|
|
|
listA, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, "list-a", clock))
|
|
require.NoError(t, err)
|
|
listB, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, "list-b", clock))
|
|
require.NoError(t, err)
|
|
|
|
const (
|
|
listAUsers = 4
|
|
listBUsers = 3
|
|
listALists = 2
|
|
listBLists = 1
|
|
)
|
|
|
|
for i := range listAUsers {
|
|
member := newAccessListMember(t, listA.GetName(), fmt.Sprintf("user-%d", i))
|
|
member.Spec.MembershipKind = accesslist.MembershipKindUser
|
|
_, err := p.accessLists.UpsertAccessListMember(ctx, member)
|
|
require.NoError(t, err)
|
|
}
|
|
for i := range listBUsers {
|
|
member := newAccessListMember(t, listB.GetName(), fmt.Sprintf("b-user-%d", i))
|
|
member.Spec.MembershipKind = accesslist.MembershipKindUser
|
|
_, err := p.accessLists.UpsertAccessListMember(ctx, member)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
for i := range listALists {
|
|
nestedList, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, fmt.Sprintf("nested-list-a-%d", i), clock))
|
|
require.NoError(t, err)
|
|
member := newAccessListMember(t, listA.GetName(), nestedList.GetName())
|
|
member.Spec.MembershipKind = accesslist.MembershipKindList
|
|
_, err = p.accessLists.UpsertAccessListMember(ctx, member)
|
|
require.NoError(t, err)
|
|
}
|
|
for i := range listBLists {
|
|
nestedList, err := p.accessLists.UpsertAccessList(ctx, newAccessList(t, fmt.Sprintf("nested-list-b-%d", i), clock))
|
|
require.NoError(t, err)
|
|
member := newAccessListMember(t, listB.GetName(), nestedList.GetName())
|
|
member.Spec.MembershipKind = accesslist.MembershipKindList
|
|
_, err = p.accessLists.UpsertAccessListMember(ctx, member)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// wait for cache to reflect updates
|
|
timeout := time.After(5 * time.Second)
|
|
for {
|
|
aUsers, aLists, err := p.cache.CountAccessListMembers(ctx, listA.GetName())
|
|
require.NoError(t, err)
|
|
bUsers, bLists, err := p.cache.CountAccessListMembers(ctx, listB.GetName())
|
|
require.NoError(t, err)
|
|
if aUsers == listAUsers && aLists == listALists && bUsers == listBUsers && bLists == listBLists {
|
|
break
|
|
}
|
|
select {
|
|
case <-timeout:
|
|
require.Fail(t, "timed out waiting for correct member counts")
|
|
case <-p.eventsC:
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetAllAccessListMembers(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
|
|
p := newTestPack(t, ForAuth)
|
|
t.Cleanup(p.Close)
|
|
|
|
clock := clockwork.NewFakeClock()
|
|
|
|
_, members, err := p.accessLists.UpsertAccessListWithMembers(context.Background(), newAccessList(t, "access-list", clock),
|
|
makeMembers(t, "access-list", 10),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
result, err := stream.Collect(clientutils.ResourcesWithPageSize(ctx, p.cache.ListAllAccessListMembers, 1))
|
|
require.NoError(t, err)
|
|
require.Len(t, result, len(members))
|
|
}, time.Second*10, time.Millisecond*30)
|
|
}
|
|
|
|
func makeMembers(t *testing.T, alName string, count int) []*accesslist.AccessListMember {
|
|
members := make([]*accesslist.AccessListMember, 0, count)
|
|
for i := 0; i < count; i++ {
|
|
members = append(members, newAccessListMember(t, alName, fmt.Sprintf("member-%d", i)))
|
|
}
|
|
return members
|
|
}
|