diff --git a/internal/email/list.go b/internal/email/list.go index 84374793..915e1a88 100644 --- a/internal/email/list.go +++ b/internal/email/list.go @@ -20,6 +20,7 @@ type ListResult struct { Count int `json:"count"` Items []TimeIndex `json:"items"` NextCursor *Cursor `json:"nextCursor"` + HasMore bool `json:"hasMore"` } // List lists emails in DynamoDB @@ -62,10 +63,9 @@ func List(ctx context.Context, api QueryAPI, input ListInput) (*ListResult, erro return nil, err } - return &ListResult{ - Count: len(result.items), - Items: result.items, - NextCursor: &Cursor{ + var nextCursor *Cursor + if result.hasMore { + nextCursor = &Cursor{ QueryInfo: QueryInfo{ Type: input.Type, Year: input.Year, @@ -73,7 +73,14 @@ func List(ctx context.Context, api QueryAPI, input ListInput) (*ListResult, erro Order: input.Order, }, LastEvaluatedKey: result.lastEvaluatedKey, - }, + } + } + + return &ListResult{ + Count: len(result.items), + Items: result.items, + NextCursor: nextCursor, + HasMore: result.hasMore, }, nil } diff --git a/internal/email/list_query.go b/internal/email/list_query.go index 3a27aed6..eebf7270 100644 --- a/internal/email/list_query.go +++ b/internal/email/list_query.go @@ -26,6 +26,7 @@ var unmarshalListOfMaps = attributevalue.UnmarshalListOfMaps type listQueryResult struct { items []TimeIndex lastEvaluatedKey map[string]types.AttributeValue + hasMore bool } // listByYearMonth returns a list of emails within a DynamoDB partition. @@ -73,5 +74,6 @@ func listByYearMonth(ctx context.Context, api QueryAPI, input listQueryInput) (l return listQueryResult{ items: items, lastEvaluatedKey: resp.LastEvaluatedKey, + hasMore: resp.LastEvaluatedKey != nil && len(resp.LastEvaluatedKey) > 0, }, nil } diff --git a/internal/email/list_test.go b/internal/email/list_test.go index e1e09764..ce42b2cf 100644 --- a/internal/email/list_test.go +++ b/internal/email/list_test.go @@ -2,6 +2,7 @@ package email import ( "context" + "errors" "strconv" "testing" "time" @@ -30,6 +31,9 @@ func TestList(t *testing.T) { "DateTime": &types.AttributeValueMemberS{Value: "12-01:01:01"}, }, }, + LastEvaluatedKey: map[string]types.AttributeValue{ + "MessageID": &types.AttributeValueMemberS{Value: "exampleMessageID"}, + }, }, nil }) }, @@ -37,6 +41,15 @@ func TestList(t *testing.T) { Type: "inbox", Year: "2022", Month: "03", + Order: "desc", + NextCursor: &Cursor{ + QueryInfo: QueryInfo{ + Type: "inbox", + Year: "2022", + Month: "03", + Order: "desc", + }, + }, }, expected: &ListResult{ Count: 1, @@ -54,7 +67,11 @@ func TestList(t *testing.T) { Month: "03", Order: "desc", }, + LastEvaluatedKey: map[string]types.AttributeValue{ + "MessageID": &types.AttributeValueMemberS{Value: "exampleMessageID"}, + }, }, + HasMore: true, }, }, { @@ -68,6 +85,9 @@ func TestList(t *testing.T) { "DateTime": &types.AttributeValueMemberS{Value: "12-01:01:01"}, }, }, + LastEvaluatedKey: map[string]types.AttributeValue{ + "MessageID": &types.AttributeValueMemberS{Value: "exampleMessageID"}, + }, }, nil }) }, @@ -91,7 +111,11 @@ func TestList(t *testing.T) { Month: "03", Order: "desc", }, + LastEvaluatedKey: map[string]types.AttributeValue{ + "MessageID": &types.AttributeValueMemberS{Value: "exampleMessageID"}, + }, }, + HasMore: true, }, }, { @@ -106,43 +130,48 @@ func TestList(t *testing.T) { }, expectedErr: ErrInvalidInput, }, - // { - // client: func(t *testing.T) QueryAPI { - // return mockQueryAPI(func(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) { - // assert.Fail(t, "this shouldn't be reached") - // return &dynamodb.QueryOutput{}, nil - // }) - // }, - // input: ListInput{ - // Type: "sent", - // Year: "0", - // }, - // expectedErr: ErrInvalidInput, - // }, - // { - // client: func(t *testing.T) QueryAPI { - // return mockQueryAPI(func(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) { - // return &dynamodb.QueryOutput{}, errors.New("error") - // }) - // }, - // input: ListInput{ - // Type: "draft", - // Year: "2022", - // Month: "3", - // }, - // expectedErr: errors.New("error"), - // }, - // { - // input: ListInput{ - // Type: "draft", - // NextCursor: &Cursor{ - // QueryInfo: QueryInfo{ - // Type: "inbox", - // }, - // }, - // }, - // expectedErr: ErrQueryNotMatch, - // }, + { + client: func(t *testing.T) QueryAPI { + return mockQueryAPI(func(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) { + assert.Fail(t, "this shouldn't be reached") + return &dynamodb.QueryOutput{}, nil + }) + }, + input: ListInput{ + Type: "sent", + Year: "0", + }, + expectedErr: ErrInvalidInput, + }, + { + client: func(t *testing.T) QueryAPI { + return mockQueryAPI(func(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) { + return &dynamodb.QueryOutput{}, errors.New("error") + }) + }, + input: ListInput{ + Type: "draft", + Year: "2022", + Month: "3", + }, + expectedErr: errors.New("error"), + }, + { + client: func(t *testing.T) QueryAPI { + return mockQueryAPI(func(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) { + return &dynamodb.QueryOutput{}, nil + }) + }, + input: ListInput{ + Type: "draft", + NextCursor: &Cursor{ + QueryInfo: QueryInfo{ + Type: "inbox", + }, + }, + }, + expectedErr: ErrQueryNotMatch, + }, } for i, test := range tests {