Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 62 additions & 5 deletions cmd/ateapi/internal/controlapi/functional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,13 +1061,12 @@ func TestListActors_PageSizeValidation(t *testing.T) {
t.Errorf("expected InvalidArgument error for negative page_size, got: %v", err)
}

// 2. Page size exceeding maxPageSize (1000)
_, err = tc.client.ListActors(context.Background(), &ateapipb.ListActorsRequest{
// 2. Page size exceeding maxPageSize (1000) is coerced silently.
if _, err := tc.client.ListActors(context.Background(), &ateapipb.ListActorsRequest{
Atespace: testAtespace,
PageSize: 1001,
})
if status.Code(err) != codes.InvalidArgument {
t.Errorf("expected InvalidArgument error for page_size > 1000, got: %v", err)
}); err != nil {
t.Errorf("expected page_size > 1000 to be coerced, got error: %v", err)
}
}

Expand Down Expand Up @@ -2295,6 +2294,64 @@ func TestValidation(t *testing.T) {
})
}
})

t.Run("ListActors", func(t *testing.T) {
tests := []struct {
name string
req *ateapipb.ListActorsRequest
wantMsg string
}{{
"invalid atespace",
&ateapipb.ListActorsRequest{Atespace: "NS1"},
"atespace: Invalid value",
}, {
"negative page_size",
&ateapipb.ListActorsRequest{Atespace: "ns1", PageSize: -1},
"page_size: Invalid value",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tc.client.ListActors(context.Background(), tt.req)
assertGrpcErrorRegex(t, err, codes.InvalidArgument, tt.wantMsg)
})
}
})

t.Run("ListWorkers", func(t *testing.T) {
tests := []struct {
name string
req *ateapipb.ListWorkersRequest
wantMsg string
}{{
"negative page_size",
&ateapipb.ListWorkersRequest{PageSize: -1},
"page_size: Invalid value",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tc.client.ListWorkers(context.Background(), tt.req)
assertGrpcErrorRegex(t, err, codes.InvalidArgument, tt.wantMsg)
})
}
})

t.Run("ListAtespaces", func(t *testing.T) {
tests := []struct {
name string
req *ateapipb.ListAtespacesRequest
wantMsg string
}{{
"negative page_size",
&ateapipb.ListAtespacesRequest{PageSize: -1},
"page_size: Invalid value",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tc.client.ListAtespaces(context.Background(), tt.req)
assertGrpcErrorRegex(t, err, codes.InvalidArgument, tt.wantMsg)
})
}
})
}

func TestResumeActor_LockConflict(t *testing.T) {
Expand Down
41 changes: 26 additions & 15 deletions cmd/ateapi/internal/controlapi/list_actors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,26 @@ import (
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/util/validation/field"
)

const maxPageSize = 1000

// effectivePageSize applies the server-chosen default for an unset page_size
// and silently coerces oversized values.
func effectivePageSize(requested int32) int32 {
if requested == 0 || requested > maxPageSize {
return maxPageSize
}
return requested
}

func (s *Service) ListActors(ctx context.Context, req *ateapipb.ListActorsRequest) (*ateapipb.ListActorsResponse, error) {
if err := validateListActorsRequest(req); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
pageSize := req.GetPageSize()
if pageSize == 0 {
pageSize = maxPageSize
return nil, err
}

actors, nextToken, err := s.persistence.ListActors(ctx, req.GetAtespace(), pageSize, req.GetPageToken())
actors, nextToken, err := s.persistence.ListActors(ctx, req.GetAtespace(), effectivePageSize(req.GetPageSize()), req.GetPageToken())
if err != nil {
return nil, fmt.Errorf("while listing actors in db: %w", err)
}
Expand All @@ -46,17 +52,22 @@ func (s *Service) ListActors(ctx context.Context, req *ateapipb.ListActorsReques
}

func validateListActorsRequest(req *ateapipb.ListActorsRequest) error {
// An empty atespace is allowed here and means "all atespaces"(used by `kubectl ate get actors -A`).
// A non-empty atespace is validated and scopes the listing to that atespace.
if req.GetAtespace() != "" && !resources.IsValidResourceName(req.GetAtespace()) {
return fmt.Errorf("invalid atespace %q: must be a valid resource name", req.GetAtespace())
var fldPath *field.Path
var errs field.ErrorList

// An empty atespace is allowed here and means "all atespaces" (used by
// `kubectl ate get actors -A`). A non-empty atespace is validated and
// scopes the listing to that atespace.
if val, fldPath := req.Atespace, fldPath.Child("atespace"); val != "" {
errs = append(errs, resources.ValidateResourceName(val, fldPath)...)
}
pageSize := req.GetPageSize()
if pageSize < 0 {
return fmt.Errorf("page_size cannot be negative")

if val, fldPath := req.PageSize, fldPath.Child("page_size"); val < 0 {
errs = append(errs, field.Invalid(fldPath, val, "must be greater than or equal to 0"))
}
if pageSize > maxPageSize {
return fmt.Errorf("page_size %d exceeds maximum page size %d", pageSize, maxPageSize)

if len(errs) > 0 {
return status.Error(codes.InvalidArgument, errs.ToAggregate().Error())
}
return nil
}
28 changes: 26 additions & 2 deletions cmd/ateapi/internal/controlapi/list_atespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,36 @@ import (
"fmt"

"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/util/validation/field"
)

func (s *Service) ListAtespaces(ctx context.Context, req *ateapipb.ListAtespacesRequest) (*ateapipb.ListAtespacesResponse, error) {
atespaces, err := s.persistence.ListAtespaces(ctx)
if err := validateListAtespacesRequest(req); err != nil {
return nil, err
}

atespaces, nextToken, err := s.persistence.ListAtespaces(ctx, effectivePageSize(req.GetPageSize()), req.GetPageToken())
if err != nil {
return nil, fmt.Errorf("while listing atespaces in db: %w", err)
}
return &ateapipb.ListAtespacesResponse{Atespaces: atespaces}, nil
return &ateapipb.ListAtespacesResponse{
Atespaces: atespaces,
NextPageToken: nextToken,
}, nil
}

func validateListAtespacesRequest(req *ateapipb.ListAtespacesRequest) error {
var fldPath *field.Path
var errs field.ErrorList

if val, fldPath := req.PageSize, fldPath.Child("page_size"); val < 0 {
errs = append(errs, field.Invalid(fldPath, val, "must be greater than or equal to 0"))
}

if len(errs) > 0 {
return status.Error(codes.InvalidArgument, errs.ToAggregate().Error())
}
return nil
}
22 changes: 18 additions & 4 deletions cmd/ateapi/internal/controlapi/list_workers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,36 @@ import (
"fmt"

"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/util/validation/field"
)

func (s *Service) ListWorkers(ctx context.Context, req *ateapipb.ListWorkersRequest) (*ateapipb.ListWorkersResponse, error) {
if err := validateListWorkersRequest(req); err != nil {
return nil, err
}
workers, err := s.persistence.ListWorkers(ctx)

workers, nextToken, err := s.persistence.ListWorkers(ctx, effectivePageSize(req.GetPageSize()), req.GetPageToken())
if err != nil {
return nil, fmt.Errorf("while listing workers in db: %w", err)
}
return &ateapipb.ListWorkersResponse{
Workers: workers,
Workers: workers,
NextPageToken: nextToken,
}, nil
}

func validateListWorkersRequest(_ *ateapipb.ListWorkersRequest) error {
// No fields to validate for now.
func validateListWorkersRequest(req *ateapipb.ListWorkersRequest) error {
var fldPath *field.Path
var errs field.ErrorList

if val, fldPath := req.PageSize, fldPath.Child("page_size"); val < 0 {
errs = append(errs, field.Invalid(fldPath, val, "must be greater than or equal to 0"))
}

if len(errs) > 0 {
return status.Error(codes.InvalidArgument, errs.ToAggregate().Error())
}
return nil
}
2 changes: 1 addition & 1 deletion cmd/ateapi/internal/controlapi/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func TestSyncer_Lifecycle(t *testing.T) {
defer cleanup()

// 1. Verify no workers in Redis initially
workers, err := persistence.ListWorkers(context.Background())
workers, _, err := persistence.ListWorkers(context.Background(), 1000, "")
if err != nil {
t.Fatalf("failed to list workers: %v", err)
}
Expand Down
Loading
Loading