You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/listtoken/list_token_test.go

1065 lines
34 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package listtoken_test
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/boundary"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_NewPaginationToken(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
tests := []struct {
name string
createdTime time.Time
typ resource.Type
grantsHash []byte
lastItemId string
lastItemCreateTime time.Time
want *listtoken.Token
wantErrString string
wantErrCode errors.Code
}{
{
name: "valid list+pagination token",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
lastItemId: "some id",
lastItemCreateTime: fiveDaysAgo,
want: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.PaginationToken{
LastItemId: "some id",
LastItemCreateTime: fiveDaysAgo,
},
},
},
{
name: "missing create time",
createdTime: time.Time{},
typ: resource.Target,
grantsHash: []byte("some hash"),
lastItemId: "some id",
lastItemCreateTime: fiveDaysAgo,
wantErrString: "missing create time",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing resource type",
createdTime: fiveDaysAgo,
typ: resource.Unknown,
grantsHash: []byte("some hash"),
lastItemId: "some id",
lastItemCreateTime: fiveDaysAgo,
wantErrString: "missing resource type",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing grants hash",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: nil,
lastItemId: "some id",
lastItemCreateTime: fiveDaysAgo,
wantErrString: "missing grants hash",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing last item id",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
lastItemId: "",
lastItemCreateTime: fiveDaysAgo,
wantErrString: "missing last item ID",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing last item create time",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
lastItemId: "some id",
lastItemCreateTime: time.Time{},
wantErrString: "missing last item create time",
wantErrCode: errors.InvalidParameter,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := listtoken.NewPagination(context.Background(), tt.createdTime, tt.typ, tt.grantsHash, tt.lastItemId, tt.lastItemCreateTime)
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
require.Empty(t, cmp.Diff(got, tt.want))
})
}
}
func Test_NewStartRefreshToken(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
tests := []struct {
name string
createdTime time.Time
typ resource.Type
grantsHash []byte
previousDeletedIdsTime time.Time
previousPhaseUpperBound time.Time
want *listtoken.Token
wantErrString string
wantErrCode errors.Code
}{
{
name: "valid list+start-refresh token",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
previousDeletedIdsTime: fiveDaysAgo,
previousPhaseUpperBound: fiveDaysAgo,
want: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.StartRefreshToken{
PreviousPhaseUpperBound: fiveDaysAgo,
PreviousDeletedIdsTime: fiveDaysAgo,
},
},
},
{
name: "missing create time",
createdTime: time.Time{},
typ: resource.Target,
grantsHash: []byte("some hash"),
previousDeletedIdsTime: fiveDaysAgo,
previousPhaseUpperBound: fiveDaysAgo,
wantErrString: "missing create time",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing resource type",
createdTime: fiveDaysAgo,
typ: resource.Unknown,
grantsHash: []byte("some hash"),
previousDeletedIdsTime: fiveDaysAgo,
previousPhaseUpperBound: fiveDaysAgo,
wantErrString: "missing resource type",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing grants hash",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: nil,
previousDeletedIdsTime: fiveDaysAgo,
previousPhaseUpperBound: fiveDaysAgo,
wantErrString: "missing grants hash",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing previous deleted ids time",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
previousDeletedIdsTime: time.Time{},
previousPhaseUpperBound: fiveDaysAgo,
wantErrString: "missing previous deleted ids time",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing previous phase upper bound",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
previousDeletedIdsTime: fiveDaysAgo,
previousPhaseUpperBound: time.Time{},
wantErrString: "missing previous phase upper bound",
wantErrCode: errors.InvalidParameter,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := listtoken.NewStartRefresh(context.Background(), tt.createdTime, tt.typ, tt.grantsHash, tt.previousDeletedIdsTime, tt.previousPhaseUpperBound)
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
require.Empty(t, cmp.Diff(got, tt.want))
})
}
}
func Test_NewRefreshToken(t *testing.T) {
t.Parallel()
timeNow := time.Now()
fiveDaysAgo := timeNow.AddDate(0, 0, -5)
fourDaysAgo := timeNow.AddDate(0, 0, -4)
tests := []struct {
name string
createdTime time.Time
typ resource.Type
grantsHash []byte
phaseUpperBound time.Time
phaseLowerBound time.Time
previousDeleteIdsTime time.Time
lastItemId string
lastItemUpdateTime time.Time
want *listtoken.Token
wantErrString string
wantErrCode errors.Code
}{
{
name: "valid list+refresh token",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
phaseUpperBound: timeNow,
phaseLowerBound: fiveDaysAgo,
previousDeleteIdsTime: fiveDaysAgo,
lastItemId: "some id",
lastItemUpdateTime: fourDaysAgo,
want: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: timeNow,
PhaseLowerBound: fiveDaysAgo,
PreviousDeletedIdsTime: fiveDaysAgo,
LastItemId: "some id",
LastItemUpdateTime: fourDaysAgo,
},
},
},
{
name: "missing create time",
createdTime: time.Time{},
typ: resource.Target,
grantsHash: []byte("some hash"),
phaseUpperBound: timeNow,
phaseLowerBound: fiveDaysAgo,
previousDeleteIdsTime: fiveDaysAgo,
lastItemId: "some id",
lastItemUpdateTime: fourDaysAgo,
wantErrString: "missing create time",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing resource type",
createdTime: fiveDaysAgo,
typ: resource.Unknown,
grantsHash: []byte("some hash"),
phaseUpperBound: timeNow,
phaseLowerBound: fiveDaysAgo,
previousDeleteIdsTime: fiveDaysAgo,
lastItemId: "some id",
lastItemUpdateTime: fourDaysAgo,
wantErrString: "missing resource type",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing grants hash",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: nil,
phaseUpperBound: timeNow,
phaseLowerBound: fiveDaysAgo,
previousDeleteIdsTime: fiveDaysAgo,
lastItemId: "some id",
lastItemUpdateTime: fourDaysAgo,
wantErrString: "missing grants hash",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing previous deleted ids time",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
phaseUpperBound: timeNow,
phaseLowerBound: fiveDaysAgo,
previousDeleteIdsTime: time.Time{},
lastItemId: "some id",
lastItemUpdateTime: fourDaysAgo,
wantErrString: "missing previous deleted ids time",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing phase upper bound",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
phaseUpperBound: time.Time{},
phaseLowerBound: fiveDaysAgo,
previousDeleteIdsTime: fiveDaysAgo,
lastItemId: "some id",
lastItemUpdateTime: fourDaysAgo,
wantErrString: "missing phase upper bound",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing phase lower bound",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
phaseUpperBound: timeNow,
phaseLowerBound: time.Time{},
previousDeleteIdsTime: fiveDaysAgo,
lastItemId: "some id",
lastItemUpdateTime: fourDaysAgo,
wantErrString: "missing phase lower bound",
wantErrCode: errors.InvalidParameter,
},
{
name: "missing last item id",
createdTime: fiveDaysAgo,
typ: resource.Target,
grantsHash: []byte("some hash"),
phaseUpperBound: timeNow,
phaseLowerBound: fiveDaysAgo,
previousDeleteIdsTime: fiveDaysAgo,
lastItemId: "",
lastItemUpdateTime: fourDaysAgo,
wantErrString: "missing last item ID",
wantErrCode: errors.InvalidParameter,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := listtoken.NewRefresh(
context.Background(),
tt.createdTime,
tt.typ,
tt.grantsHash,
tt.previousDeleteIdsTime,
tt.phaseUpperBound,
tt.phaseLowerBound,
tt.lastItemId,
tt.lastItemUpdateTime,
)
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
require.Empty(t, cmp.Diff(got, tt.want))
})
}
}
func Test_ValidateListToken(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
tests := []struct {
name string
token *listtoken.Token
grantsHash []byte
resourceType resource.Type
wantErrString string
wantErrCode errors.Code
}{
{
name: "valid token",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
},
{
name: "nil token",
token: nil,
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token was missing",
wantErrCode: errors.InvalidListToken,
},
{
name: "no grants hash",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: nil,
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token was missing its grants hash",
wantErrCode: errors.InvalidListToken,
},
{
name: "changed grants hash",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
},
grantsHash: []byte("some other hash"),
resourceType: resource.Target,
wantErrString: "grants have changed since list token was issued",
wantErrCode: errors.InvalidListToken,
},
{
name: "expired",
token: &listtoken.Token{
CreateTime: time.Now().AddDate(0, 0, -31),
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token was expired",
wantErrCode: errors.InvalidListToken,
},
{
name: "resource type mismatch",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
},
grantsHash: []byte("some hash"),
resourceType: resource.SessionRecording,
wantErrString: "list token resource type does not match expected resource type",
wantErrCode: errors.InvalidListToken,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.token.Validate(context.Background(), tt.resourceType, tt.grantsHash)
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
})
}
}
func Test_ValidatePaginationToken(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
tests := []struct {
name string
token *listtoken.Token
grantsHash []byte
resourceType resource.Type
wantErrString string
wantErrCode errors.Code
}{
{
name: "valid token",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.PaginationToken{
LastItemId: "s_1234567890",
LastItemCreateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
},
{
name: "last item ID unset",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.PaginationToken{
LastItemId: "",
LastItemCreateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list tokens's pagination component missing last item ID",
wantErrCode: errors.InvalidListToken,
},
{
name: "updated in the future",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.PaginationToken{
LastItemId: "s_1234567890",
LastItemCreateTime: time.Now().AddDate(1, 0, 0),
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's pagination component's last item was created after the token",
wantErrCode: errors.InvalidListToken,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.token.Validate(context.Background(), tt.resourceType, tt.grantsHash)
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
})
}
}
func Test_ValidateStartRefreshToken(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
tests := []struct {
name string
token *listtoken.Token
grantsHash []byte
resourceType resource.Type
wantErrString string
wantErrCode errors.Code
}{
{
name: "valid token",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.StartRefreshToken{
PreviousDeletedIdsTime: fiveDaysAgo,
PreviousPhaseUpperBound: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
},
{
name: "previous phase upper bound before create time",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.StartRefreshToken{
PreviousDeletedIdsTime: fiveDaysAgo,
PreviousPhaseUpperBound: fiveDaysAgo.AddDate(-1, 0, 0),
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's start refresh component's previous phase upper bound was before its creation time",
wantErrCode: errors.InvalidListToken,
},
{
name: "previous deleted ids time before create time",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.StartRefreshToken{
PreviousDeletedIdsTime: fiveDaysAgo.AddDate(-1, 0, 0),
PreviousPhaseUpperBound: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's start refresh component previous deleted ids time was before its creation time",
wantErrCode: errors.InvalidListToken,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.token.Validate(context.Background(), tt.resourceType, tt.grantsHash)
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
})
}
}
func Test_ValidateRefreshToken(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
tests := []struct {
name string
token *listtoken.Token
grantsHash []byte
resourceType resource.Type
wantErrString string
wantErrCode errors.Code
}{
{
name: "valid token",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: fiveDaysAgo,
PreviousDeletedIdsTime: fiveDaysAgo,
PhaseLowerBound: fiveDaysAgo,
LastItemId: "some id",
LastItemUpdateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
},
{
name: "phase upper bound before create time",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: fiveDaysAgo.AddDate(-1, 0, 0),
PreviousDeletedIdsTime: fiveDaysAgo,
PhaseLowerBound: fiveDaysAgo,
LastItemId: "some id",
LastItemUpdateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's refresh component's phase upper bound was before its creation time",
wantErrCode: errors.InvalidListToken,
},
{
name: "phase upper bound before phase lower bound",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: fiveDaysAgo.AddDate(0, 0, 1),
PreviousDeletedIdsTime: fiveDaysAgo,
PhaseLowerBound: fiveDaysAgo.AddDate(0, 0, 2),
LastItemId: "some id",
LastItemUpdateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's refresh component's phase upper bound was before the phase lower bound",
wantErrCode: errors.InvalidListToken,
},
{
name: "phase lower bound before create time",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: fiveDaysAgo,
PreviousDeletedIdsTime: fiveDaysAgo,
PhaseLowerBound: fiveDaysAgo.AddDate(-1, 0, 0),
LastItemId: "some id",
LastItemUpdateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's refresh component's phase lower bound was before its creation time",
wantErrCode: errors.InvalidListToken,
},
{
name: "previous deleted ids time before create time",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: fiveDaysAgo,
PreviousDeletedIdsTime: fiveDaysAgo.AddDate(-1, 0, 0),
PhaseLowerBound: fiveDaysAgo,
LastItemId: "some id",
LastItemUpdateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's refresh component previous deleted ids time was before its creation time",
wantErrCode: errors.InvalidListToken,
},
{
name: "emtpy last item id",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: fiveDaysAgo,
PreviousDeletedIdsTime: fiveDaysAgo,
PhaseLowerBound: fiveDaysAgo,
LastItemId: "",
LastItemUpdateTime: fiveDaysAgo,
},
},
grantsHash: []byte("some hash"),
resourceType: resource.Target,
wantErrString: "list token's refresh component missing last item ID",
wantErrCode: errors.InvalidListToken,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.token.Validate(context.Background(), tt.resourceType, tt.grantsHash)
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
})
}
}
func TestToken_LastItem(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
tests := []struct {
name string
token *listtoken.Token
wantItemPublicId string
wantItemCreateTime *timestamp.Timestamp
wantItemUpdateTime *timestamp.Timestamp
wantResourceType resource.Type
wantErrString string
wantErrCode errors.Code
}{
{
name: "refresh token returns item with update time",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.RefreshToken{
PhaseUpperBound: fiveDaysAgo.Add(time.Hour),
PreviousDeletedIdsTime: fiveDaysAgo,
PhaseLowerBound: fiveDaysAgo,
LastItemId: "some id",
LastItemUpdateTime: fiveDaysAgo,
},
},
wantItemPublicId: "some id",
wantItemCreateTime: nil,
wantItemUpdateTime: timestamp.New(fiveDaysAgo),
wantResourceType: resource.Target,
},
{
name: "pagination token returns item with create time",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.PaginationToken{
LastItemId: "some id",
LastItemCreateTime: fiveDaysAgo,
},
},
wantItemPublicId: "some id",
wantItemCreateTime: timestamp.New(fiveDaysAgo),
wantItemUpdateTime: nil,
wantResourceType: resource.Target,
},
{
name: "start refresh token returns no item",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: &listtoken.StartRefreshToken{
PreviousPhaseUpperBound: fiveDaysAgo,
PreviousDeletedIdsTime: fiveDaysAgo,
},
},
wantErrString: "start refresh tokens have no last item",
wantErrCode: errors.Internal,
},
{
name: "nil subtype returns no item",
token: &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: nil,
},
wantErrString: "unexpected token subtype",
wantErrCode: errors.Internal,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
item, err := tt.token.LastItem(context.Background())
if tt.wantErrString != "" {
require.ErrorContains(t, err, tt.wantErrString)
require.Equal(t, errors.Convert(err).Code, tt.wantErrCode)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantResourceType, item.GetResourceType())
assert.Equal(t, tt.wantItemCreateTime, item.GetCreateTime())
assert.Equal(t, tt.wantItemPublicId, item.GetPublicId())
assert.Equal(t, tt.wantItemUpdateTime, item.GetUpdateTime())
})
}
}
func TestToken_Transition(t *testing.T) {
t.Parallel()
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
t.Run("pagination token without complete listing transitions to pagination token", func(t *testing.T) {
t.Parallel()
createTime := fiveDaysAgo.Add(-time.Hour)
tk, err := listtoken.NewPagination(
context.Background(),
fiveDaysAgo,
resource.Target,
[]byte("some hash"),
"some id",
createTime,
)
require.NoError(t, err)
lastItem := &fakeTarget{
publicId: "some other id",
createTime: timestamp.New(fiveDaysAgo.Add(time.Hour)),
}
deletedIdsTime := fiveDaysAgo.Add(2 * time.Hour)
phaseUpperBound := fiveDaysAgo.Add(3 * time.Hour)
err = tk.Transition(context.Background(), false, lastItem, deletedIdsTime, phaseUpperBound)
require.NoError(t, err)
st, ok := tk.Subtype.(*listtoken.PaginationToken)
require.True(t, ok, "Subtype was %T, not PaginationToken", tk.Subtype)
assert.Equal(t, lastItem.createTime.AsTime(), st.LastItemCreateTime)
assert.Equal(t, lastItem.publicId, st.LastItemId)
})
t.Run("pagination token with complete listing transitions to start refresh", func(t *testing.T) {
t.Parallel()
createTime := fiveDaysAgo.Add(-time.Hour)
tk, err := listtoken.NewPagination(
context.Background(),
fiveDaysAgo,
resource.Target,
[]byte("some hash"),
"some id",
createTime,
)
require.NoError(t, err)
lastItem := &fakeTarget{
publicId: "some other id",
createTime: timestamp.New(fiveDaysAgo.Add(time.Hour)),
}
deletedIdsTime := fiveDaysAgo.Add(2 * time.Hour)
phaseUpperBound := fiveDaysAgo.Add(3 * time.Hour)
err = tk.Transition(context.Background(), true, lastItem, deletedIdsTime, phaseUpperBound)
require.NoError(t, err)
st, ok := tk.Subtype.(*listtoken.StartRefreshToken)
require.True(t, ok, "Subtype was %T, not StartRefreshToken", tk.Subtype)
assert.Equal(t, fiveDaysAgo, st.PreviousDeletedIdsTime)
assert.Equal(t, fiveDaysAgo, st.PreviousPhaseUpperBound)
})
t.Run("start refresh token without complete listing transitions to refresh token", func(t *testing.T) {
t.Parallel()
deletedIdsTime := fiveDaysAgo.Add(2 * time.Hour)
phaseUpperBound := fiveDaysAgo.Add(3 * time.Hour)
tk, err := listtoken.NewStartRefresh(
context.Background(),
fiveDaysAgo,
resource.Target,
[]byte("some hash"),
deletedIdsTime,
phaseUpperBound,
)
require.NoError(t, err)
lastItem := &fakeTarget{
publicId: "some other id",
updateTime: timestamp.New(fiveDaysAgo.Add(time.Hour)),
}
newDeletedIdsTime := fiveDaysAgo.Add(4 * time.Hour)
newPhaseUpperBound := fiveDaysAgo.Add(5 * time.Hour)
err = tk.Transition(context.Background(), false, lastItem, newDeletedIdsTime, newPhaseUpperBound)
require.NoError(t, err)
st, ok := tk.Subtype.(*listtoken.RefreshToken)
require.True(t, ok, "Subtype was %T, not RefreshToken", tk.Subtype)
assert.Equal(t, lastItem.updateTime.AsTime(), st.LastItemUpdateTime)
assert.Equal(t, lastItem.publicId, st.LastItemId)
assert.Equal(t, phaseUpperBound, st.PhaseLowerBound)
assert.Equal(t, newPhaseUpperBound, st.PhaseUpperBound)
assert.Equal(t, newDeletedIdsTime, st.PreviousDeletedIdsTime)
})
t.Run("start refresh token with complete listing transitions to start refresh", func(t *testing.T) {
t.Parallel()
deletedIdsTime := fiveDaysAgo.Add(2 * time.Hour)
phaseUpperBound := fiveDaysAgo.Add(3 * time.Hour)
tk, err := listtoken.NewStartRefresh(
context.Background(),
fiveDaysAgo,
resource.Target,
[]byte("some hash"),
deletedIdsTime,
phaseUpperBound,
)
require.NoError(t, err)
lastItem := &fakeTarget{
publicId: "some other id",
createTime: timestamp.New(fiveDaysAgo.Add(time.Hour)),
}
newDeletedIdsTime := fiveDaysAgo.Add(4 * time.Hour)
newPhaseUpperBound := fiveDaysAgo.Add(5 * time.Hour)
err = tk.Transition(context.Background(), true, lastItem, newDeletedIdsTime, newPhaseUpperBound)
require.NoError(t, err)
st, ok := tk.Subtype.(*listtoken.StartRefreshToken)
require.True(t, ok, "Subtype was %T, not StartRefreshToken", tk.Subtype)
assert.Equal(t, newDeletedIdsTime, st.PreviousDeletedIdsTime)
assert.Equal(t, newPhaseUpperBound, st.PreviousPhaseUpperBound)
})
t.Run("refresh token without complete listing transitions to refresh token", func(t *testing.T) {
t.Parallel()
deletedIdsTime := fiveDaysAgo.Add(2 * time.Hour)
phaseLowerBound := fiveDaysAgo.Add(3 * time.Hour)
phaseUpperBound := fiveDaysAgo.Add(4 * time.Hour)
updateTime := fiveDaysAgo.Add(5 * time.Hour)
tk, err := listtoken.NewRefresh(
context.Background(),
fiveDaysAgo,
resource.Target,
[]byte("some hash"),
deletedIdsTime,
phaseUpperBound,
phaseLowerBound,
"some id",
updateTime,
)
require.NoError(t, err)
lastItem := &fakeTarget{
publicId: "some other id",
updateTime: timestamp.New(fiveDaysAgo.Add(time.Hour)),
}
newDeletedIdsTime := fiveDaysAgo.Add(6 * time.Hour)
newPhaseUpperBound := fiveDaysAgo.Add(7 * time.Hour)
err = tk.Transition(context.Background(), false, lastItem, newDeletedIdsTime, newPhaseUpperBound)
require.NoError(t, err)
st, ok := tk.Subtype.(*listtoken.RefreshToken)
require.True(t, ok, "Subtype was %T, not RefreshToken", tk.Subtype)
assert.Equal(t, lastItem.updateTime.AsTime(), st.LastItemUpdateTime)
assert.Equal(t, lastItem.publicId, st.LastItemId)
assert.Equal(t, phaseLowerBound, st.PhaseLowerBound)
assert.Equal(t, phaseUpperBound, st.PhaseUpperBound)
assert.Equal(t, newDeletedIdsTime, st.PreviousDeletedIdsTime)
})
t.Run("refresh token with complete listing transitions to start refresh", func(t *testing.T) {
t.Parallel()
deletedIdsTime := fiveDaysAgo.Add(2 * time.Hour)
phaseLowerBound := fiveDaysAgo.Add(3 * time.Hour)
phaseUpperBound := fiveDaysAgo.Add(4 * time.Hour)
updateTime := fiveDaysAgo.Add(5 * time.Hour)
tk, err := listtoken.NewRefresh(
context.Background(),
fiveDaysAgo,
resource.Target,
[]byte("some hash"),
deletedIdsTime,
phaseUpperBound,
phaseLowerBound,
"some id",
updateTime,
)
require.NoError(t, err)
lastItem := &fakeTarget{
publicId: "some other id",
createTime: timestamp.New(fiveDaysAgo.Add(time.Hour)),
}
newDeletedIdsTime := fiveDaysAgo.Add(6 * time.Hour)
newPhaseUpperBound := fiveDaysAgo.Add(7 * time.Hour)
err = tk.Transition(context.Background(), true, lastItem, newDeletedIdsTime, newPhaseUpperBound)
require.NoError(t, err)
st, ok := tk.Subtype.(*listtoken.StartRefreshToken)
require.True(t, ok, "Subtype was %T, not StartRefreshToken", tk.Subtype)
assert.Equal(t, newDeletedIdsTime, st.PreviousDeletedIdsTime)
assert.Equal(t, phaseUpperBound, st.PreviousPhaseUpperBound)
})
t.Run("token without subtype errors", func(t *testing.T) {
t.Parallel()
tk := &listtoken.Token{
CreateTime: fiveDaysAgo,
ResourceType: resource.Target,
GrantsHash: []byte("some hash"),
Subtype: nil,
}
lastItem := &fakeTarget{
publicId: "some other id",
createTime: timestamp.New(fiveDaysAgo.Add(time.Hour)),
}
newDeletedIdsTime := fiveDaysAgo.Add(6 * time.Hour)
newPhaseUpperBound := fiveDaysAgo.Add(7 * time.Hour)
err := tk.Transition(context.Background(), true, lastItem, newDeletedIdsTime, newPhaseUpperBound)
require.Error(t, err)
assert.ErrorContains(t, err, "unexpected token subtype")
})
}
type fakeTarget struct {
boundary.Resource
publicId string
updateTime *timestamp.Timestamp
createTime *timestamp.Timestamp
}
func (m *fakeTarget) GetResourceType() resource.Type {
return resource.Target
}
func (m *fakeTarget) GetPublicId() string {
return m.publicId
}
func (m *fakeTarget) GetUpdateTime() *timestamp.Timestamp {
return m.updateTime
}
func (m *fakeTarget) GetCreateTime() *timestamp.Timestamp {
return m.createTime
}