Files
threshold/internal/pagination/plugin/pagination_plugins_test.go
T
Johan Brandhorst-Satzkorn 8175086be7 internal/pagination: avoid import cycle
The iam package is very foundational, and now depends
on the pagination package. Thus, the pagination package
must not import any packages that may import iam,
such as the plugin package. Move the plugin helpers
to a subdirectory to avoid this import cycle.
2024-01-05 07:43:21 -08:00

3881 lines
155 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package plugin
import (
"context"
"errors"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
"github.com/hashicorp/boundary/internal/plugin"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ListPlugins(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("validation", func(t *testing.T) {
t.Parallel()
t.Run("empty grants hash", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte(nil)
_, _, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
pageSize := 0
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
pageSize := -1
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter item callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := ListPluginsFilterFunc[*testType](nil)
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil list items callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := ListPluginsItemsFunc[*testType](nil)
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "missing list items callback")
})
t.Run("nil estimated count callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := pagination.EstimatedCountFunc(nil)
grantsHash := []byte("some hash")
_, _, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "missing estimated count callback")
})
})
t.Run("error-cases", func(t *testing.T) {
t.Parallel()
t.Run("errors-when-list-errors-immediately", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
return nil, nil, time.Time{}, errors.New("failed to list")
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-list-errors-subsequently", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
return nil, nil, time.Time{}, errors.New("failed to list")
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-filter-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return false, errors.New("failed to filter")
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "failed to filter")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-estimated-count-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 0, errors.New("failed to estimate count")
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.ErrorContains(t, err, "failed to estimate count")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
})
t.Run("no-rows", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 0)
// No response token expected when there were no results
assert.Nil(t, resp.ListToken)
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemId, "2")
assert.True(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemCreateTime.Equal(lastItemCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 2)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(listReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemCreateTime.Equal(lastItemCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemCreateTime.Equal(lastItemCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 2)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(listReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 1)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(listReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-with-full-last-page", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 1)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(listReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("filter-everything", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
// Filter every item
return false, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 0)
assert.Nil(t, resp.ListToken)
assert.Equal(t, plgsMap, plgs)
})
t.Run("appends-and-deduplicates-plugins-between-invocation", func(t *testing.T) {
t.Parallel()
pageSize := 2
plg1 := plugin.NewPlugin()
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin()
plg2.PublicId = "id2"
plg3 := plugin.NewPlugin()
plg3.PublicId = "id3"
origPlgs := []*plugin.Plugin{plg1, plg2}
otherPlgs := []*plugin.Plugin{plg2, plg3}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, otherPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPlugins(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 2)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(listReturnTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(listReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(
t,
map[string]*plugin.Plugin{plg1.PublicId: plg1, plg2.PublicId: plg2, plg3.PublicId: plg3},
plgs,
)
})
}
func Test_ListPluginsPage(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("validation", func(t *testing.T) {
t.Parallel()
t.Run("empty grants hash", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte(nil)
_, _, err = ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
pageSize := 0
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
pageSize := -1
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter item callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := ListPluginsFilterFunc[*testType](nil)
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil list items callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := ListPluginsItemsFunc[*testType](nil)
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "missing list items callback")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, nil)
require.ErrorContains(t, err, "missing list token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "token did not have a pagination token component")
})
t.Run("nil estimated count callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := pagination.EstimatedCountFunc(nil)
grantsHash := []byte("some hash")
_, _, err = ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "missing estimated count callback")
})
})
t.Run("error-cases", func(t *testing.T) {
t.Run("errors-when-list-errors-immediately", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
return nil, nil, time.Time{}, errors.New("failed to list")
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-list-errors-subsequently", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
return nil, nil, time.Time{}, errors.New("failed to list")
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-filter-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return false, errors.New("failed to filter")
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "failed to filter")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-estimated-count-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 0, errors.New("failed to estimate count")
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.ErrorContains(t, err, "failed to estimate count")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
})
t.Run("no-rows", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(tokenCreateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(tokenCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemId, "2")
assert.True(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemCreateTime.Equal(lastItemCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(tokenCreateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(tokenCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemCreateTime.Equal(lastItemCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.PaginationToken).LastItemCreateTime.Equal(lastItemCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(tokenCreateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(tokenCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(tokenCreateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(tokenCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-with-full-last-page", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(tokenCreateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(tokenCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("filter-everything", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
// Filter every item
return false, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(tokenCreateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(tokenCreateTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("appends-and-deduplicates-plugins-between-invocation", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin()
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin()
plg2.PublicId = "id2"
plg3 := plugin.NewPlugin()
plg3.PublicId = "id3"
origPlgs := []*plugin.Plugin{plg1, plg2}
otherPlgs := []*plugin.Plugin{plg2, plg3}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, otherPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(tokenCreateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(tokenCreateTime))
assert.Equal(
t,
map[string]*plugin.Plugin{plg1.PublicId: plg1, plg2.PublicId: plg2, plg3.PublicId: plg3},
plgs,
)
})
}
func Test_ListPluginsRefresh(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("validation", func(t *testing.T) {
t.Parallel()
t.Run("empty grants hash", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte(nil)
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
pageSize := 0
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
pageSize := -1
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter item callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := ListPluginsFilterFunc[*testType](nil)
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil list items callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := ListPluginsItemsFunc[*testType](nil)
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing list items callback")
})
t.Run("nil list deleted ids callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := pagination.ListDeletedIDsFunc(nil)
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing list deleted IDs callback")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, nil)
require.ErrorContains(t, err, "missing list token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "token did not have a start-refresh token component")
})
t.Run("nil estimated count callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := pagination.EstimatedCountFunc(nil)
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing estimated count callback")
})
})
t.Run("error-cases", func(t *testing.T) {
t.Run("errors-when-list-errors-immediately", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
return nil, nil, time.Time{}, errors.New("failed to list")
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-list-errors-subsequently", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
return nil, nil, time.Time{}, errors.New("failed to list")
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-filter-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return false, errors.New("failed to filter")
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to filter")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-estimated-count-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 0, errors.New("failed to estimate count")
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to estimate count")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-list-deleted-ids-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, time.Time{}, errors.New("failed to list deleted IDs")
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to list deleted IDs")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
})
t.Run("no-rows", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemId, "2")
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemUpdateTime.Equal(lastItemUpdateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseLowerBound.Equal(prevPhaseUpperBound))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemUpdateTime.Equal(lastItemUpdateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseLowerBound.Equal(prevPhaseUpperBound))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemUpdateTime.Equal(lastItemUpdateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseLowerBound.Equal(prevPhaseUpperBound))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-with-full-last-page", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("filter-everything", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
// Filter every item
return false, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(t, plgsMap, plgs)
})
t.Run("appends-and-deduplicates-plugins-between-invocation", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewStartRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
prevPhaseUpperBound,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin()
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin()
plg2.PublicId = "id2"
plg3 := plugin.NewPlugin()
plg3.PublicId = "id3"
origPlgs := []*plugin.Plugin{plg1, plg2}
otherPlgs := []*plugin.Plugin{plg2, plg3}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, otherPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(listReturnTime))
assert.Equal(
t,
map[string]*plugin.Plugin{plg1.PublicId: plg1, plg2.PublicId: plg2, plg3.PublicId: plg3},
plgs,
)
})
}
func Test_ListPluginsRefreshPage(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("validation", func(t *testing.T) {
t.Parallel()
t.Run("empty grants hash", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte(nil)
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
pageSize := 0
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
pageSize := -1
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter item callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := ListPluginsFilterFunc[*testType](nil)
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil list items callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := ListPluginsItemsFunc[*testType](nil)
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing list items callback")
})
t.Run("nil list deleted ids callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := pagination.ListDeletedIDsFunc(nil)
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing list deleted IDs callback")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
pageSize := 2
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, nil)
require.ErrorContains(t, err, "missing list token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewPagination(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
"some id",
lastItemCreateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "token did not have a refresh token component")
})
t.Run("nil estimated count callback", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, nil, time.Time{}, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := pagination.EstimatedCountFunc(nil)
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
_, _, err = ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "missing estimated count callback")
})
})
t.Run("error-cases", func(t *testing.T) {
t.Run("errors-when-list-errors-immediately", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
return nil, nil, time.Time{}, errors.New("failed to list")
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-list-errors-subsequently", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
return nil, nil, time.Time{}, errors.New("failed to list")
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to list")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-filter-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return false, errors.New("failed to filter")
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to filter")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-estimated-count-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 0, errors.New("failed to estimate count")
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to estimate count")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
t.Run("errors-when-list-deleted-ids-errors", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, time.Time{}, errors.New("failed to list deleted IDs")
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.ErrorContains(t, err, "failed to list deleted IDs")
assert.Empty(t, resp)
assert.Empty(t, plgs)
})
})
t.Run("no-rows", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return nil, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return nil, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Empty(t, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
{nil, "3", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemId, "2")
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemUpdateTime.Equal(lastItemUpdateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseLowerBound.Equal(phaseLowerBound))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-first-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
assert.Nil(t, prevPageLast)
return []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "2", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-with-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemUpdateTime.Equal(lastItemUpdateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseLowerBound.Equal(phaseLowerBound))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.False(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.Equal(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemId, "3")
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).LastItemUpdateTime.Equal(lastItemUpdateTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseLowerBound.Equal(phaseLowerBound))
assert.True(t, resp.ListToken.Subtype.(*listtoken.RefreshToken).PhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("fill-on-subsequent", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-without-remaining", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("dont-fill-with-full-last-page", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID != "1" {
// Filter every item except the first
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("filter-everything", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin(plugin.WithName("plugin-1"))
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin(plugin.WithName("plugin-2"))
plg2.PublicId = "id2"
plgsMap := map[string]*plugin.Plugin{
plg1.PublicId: plg1,
plg2.PublicId: plg2,
}
origPlgs := []*plugin.Plugin{plg1, plg2}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
switch {
case prevPageLast == nil:
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
case prevPageLast.ID == "3":
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
{nil, "5", lastItemCreateTime.Add(-2 * time.Second), lastItemUpdateTime.Add(-2 * time.Second)},
{nil, "6", lastItemCreateTime.Add(-3 * time.Second), lastItemUpdateTime.Add(-3 * time.Second)},
}, origPlgs, listReturnTime.Add(time.Second), nil
case prevPageLast.ID == "6":
return nil, origPlgs, listReturnTime.Add(2 * time.Second), nil
default:
t.Fatalf("unexpected call to listItemsFn with %#v", prevPageLast)
return nil, nil, time.Time{}, nil
}
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
// Filter every item
return false, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, resp.Items)
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(t, plgsMap, plgs)
})
t.Run("appends-and-deduplicates-plugins-between-invocation", func(t *testing.T) {
t.Parallel()
pageSize := 2
tok, err := listtoken.NewRefresh(
ctx,
tokenCreateTime,
resource.Unknown,
[]byte("some hash"),
prevDeletedTime,
phaseUpperBound,
phaseLowerBound,
"some id",
lastItemUpdateTime,
)
require.NoError(t, err)
plg1 := plugin.NewPlugin()
plg1.PublicId = "id1"
plg2 := plugin.NewPlugin()
plg2.PublicId = "id2"
plg3 := plugin.NewPlugin()
plg3.PublicId = "id3"
origPlgs := []*plugin.Plugin{plg1, plg2}
otherPlgs := []*plugin.Plugin{plg2, plg3}
listItemsFn := func(ctx context.Context, prevPageLast *testType, limit int) ([]*testType, []*plugin.Plugin, time.Time, error) {
if prevPageLast != nil {
assert.Equal(t, "3", prevPageLast.ID)
return []*testType{
{nil, "4", lastItemCreateTime.Add(-time.Second), lastItemUpdateTime.Add(-time.Second)},
}, otherPlgs, listReturnTime.Add(time.Second), nil
}
return []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "2", lastItemCreateTime.Add(time.Second), lastItemUpdateTime.Add(time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}, origPlgs, listReturnTime, nil
}
filterItemFn := func(ctx context.Context, item *testType, plgs map[string]*plugin.Plugin) (bool, error) {
if item.ID == "2" || item.ID == "4" || item.ID == "6" {
// Filter every other item
return false, nil
}
return true, nil
}
estimatedItemCountFn := func(ctx context.Context) (int, error) {
return 10, nil
}
deletedIDsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
return []string{"deleted-id"}, deletedIDsReturnTime, nil
}
grantsHash := []byte("some hash")
resp, plgs, err := ListPluginsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, estimatedItemCountFn, deletedIDsFn, tok)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(resp.Items, []*testType{
{nil, "1", lastItemCreateTime.Add(2 * time.Second), lastItemUpdateTime.Add(2 * time.Second)},
{nil, "3", lastItemCreateTime, lastItemUpdateTime},
}))
assert.True(t, resp.CompleteListing)
assert.Equal(t, []string{"deleted-id"}, resp.DeletedIds)
assert.Equal(t, resp.EstimatedItemCount, 10)
require.NotNil(t, resp.ListToken)
assert.True(t, resp.ListToken.CreateTime.Equal(tokenCreateTime))
assert.Equal(t, resp.ListToken.GrantsHash, grantsHash)
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousDeletedIdsTime.Equal(deletedIDsReturnTime))
assert.True(t, resp.ListToken.Subtype.(*listtoken.StartRefreshToken).PreviousPhaseUpperBound.Equal(phaseUpperBound))
assert.Equal(
t,
map[string]*plugin.Plugin{plg1.PublicId: plg1, plg2.PublicId: plg2, plg3.PublicId: plg3},
plgs,
)
})
}