go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/rpcs/tasks_count_tasks_test.go (about)

     1  // Copyright 2024 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rpcs
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/protobuf/types/known/timestamppb"
    24  
    25  	"go.chromium.org/luci/auth/identity"
    26  	"go.chromium.org/luci/gae/impl/memory"
    27  	"go.chromium.org/luci/gae/service/datastore"
    28  
    29  	apipb "go.chromium.org/luci/swarming/proto/api_v2"
    30  	"go.chromium.org/luci/swarming/server/acls"
    31  	"go.chromium.org/luci/swarming/server/model"
    32  
    33  	. "github.com/smartystreets/goconvey/convey"
    34  	. "go.chromium.org/luci/common/testing/assertions"
    35  )
    36  
    37  func createMockTaskResult(ctx context.Context, tags []string, state apipb.TaskState, testTime time.Time) *model.TaskResultSummary {
    38  	reqKey, err := model.TimestampToRequestKey(ctx, testTime.Add(-2*time.Hour), 0)
    39  	if err != nil {
    40  		panic(err)
    41  	}
    42  	tags = append(tags, "pool:example-pool")
    43  	trs := &model.TaskResultSummary{
    44  		TaskResultCommon: model.TaskResultCommon{
    45  			State:            state,
    46  			Modified:         testTime,
    47  			BotVersion:       "bot_version_123",
    48  			BotDimensions:    model.BotDimensions{"os": []string{"linux"}, "cpu": []string{"x86_64"}},
    49  			CurrentTaskSlice: 1,
    50  			Started:          datastore.NewIndexedNullable(testTime.Add(-1 * time.Hour)),
    51  			Completed:        datastore.NewIndexedNullable(testTime),
    52  			DurationSecs:     datastore.NewUnindexedOptional(3600.0),
    53  			ExitCode:         datastore.NewUnindexedOptional(int64(0)),
    54  			Failure:          false,
    55  			InternalFailure:  false,
    56  		},
    57  		Key:                  model.TaskResultSummaryKey(ctx, reqKey),
    58  		BotID:                datastore.NewUnindexedOptional("bot123"),
    59  		Created:              testTime.Add(-2 * time.Hour),
    60  		Tags:                 tags,
    61  		RequestName:          "example-request",
    62  		RequestUser:          "user@example.com",
    63  		RequestPriority:      50,
    64  		RequestAuthenticated: "authenticated-user@example.com",
    65  		RequestRealm:         "project:visible-realm",
    66  		RequestPool:          "example-pool",
    67  		RequestBotID:         "bot123",
    68  		PropertiesHash:       datastore.NewIndexedOptional([]byte("prop-hash")),
    69  		TryNumber:            datastore.NewIndexedNullable(int64(1)),
    70  		CostUSD:              0.05,
    71  		CostSavedUSD:         0.00,
    72  		DedupedFrom:          "",
    73  		ExpirationDelay:      datastore.NewUnindexedOptional(0.0),
    74  	}
    75  	return trs
    76  }
    77  
    78  func TestCountTasks(t *testing.T) {
    79  	t.Parallel()
    80  
    81  	Convey("TestCountTasks", t, func() {
    82  		ctx := memory.Use(context.Background())
    83  		datastore.GetTestable(ctx).Consistent(true)
    84  		state := NewMockedRequestState()
    85  		state.MockPool("example-pool", "project:visible-realm")
    86  		state.MockPerm("project:visible-realm", acls.PermPoolsListTasks)
    87  		srv := TasksServer{TaskQuerySplitMode: model.SplitCompletely}
    88  		testTime := time.Date(2023, 1, 1, 2, 3, 4, 0, time.UTC)
    89  
    90  		Convey("ok; no tags in req", func() {
    91  			one := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime)
    92  			two := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime.Add(time.Minute*10))
    93  			So(datastore.Put(ctx, one, two), ShouldBeNil)
    94  			req := &apipb.TasksCountRequest{
    95  				Start: timestamppb.New(testTime.Add(-10 * time.Hour)),
    96  				State: apipb.StateQuery_QUERY_ALL,
    97  			}
    98  			resp, err := srv.CountTasks(MockRequestState(ctx, state.SetCaller(AdminFakeCaller)), req)
    99  			So(err, ShouldBeNil)
   100  			So(resp.Count, ShouldEqual, 2)
   101  			So(resp.Now, ShouldNotBeNil)
   102  		})
   103  
   104  		Convey("ok; tags in req", func() {
   105  			tags := []string{"os:ubuntu", "os:ubuntu22"}
   106  			one := createMockTaskResult(ctx, tags, apipb.TaskState_CANCELED, testTime)
   107  			two := createMockTaskResult(ctx, tags, apipb.TaskState_CANCELED, testTime.Add(time.Minute*10))
   108  			So(datastore.Put(ctx, one, two), ShouldBeNil)
   109  			req := &apipb.TasksCountRequest{
   110  				Start: timestamppb.New(testTime.Add(-10 * time.Hour)),
   111  				State: apipb.StateQuery_QUERY_ALL,
   112  				Tags:  []string{"pool:example-pool", "os:ubuntu|ubuntu22"},
   113  			}
   114  			resp, err := srv.CountTasks(MockRequestState(ctx, state), req)
   115  			So(err, ShouldBeNil)
   116  			So(resp.Count, ShouldEqual, 2)
   117  			So(resp.Now, ShouldNotBeNil)
   118  		})
   119  
   120  		Convey("ok; tags in req; no entities", func() {
   121  			tags := []string{"os:ubuntu"}
   122  			one := createMockTaskResult(ctx, tags, apipb.TaskState_CANCELED, testTime)
   123  			two := createMockTaskResult(ctx, tags, apipb.TaskState_CANCELED, testTime.Add(time.Minute*10))
   124  			So(datastore.Put(ctx, one, two), ShouldBeNil)
   125  			req := &apipb.TasksCountRequest{
   126  				Start: timestamppb.New(testTime.Add(-10 * time.Hour)),
   127  				State: apipb.StateQuery_QUERY_ALL,
   128  				Tags:  []string{"pool:example-pool", "os:ubuntu15|ubuntu22"},
   129  			}
   130  			resp, err := srv.CountTasks(MockRequestState(ctx, state), req)
   131  			So(err, ShouldBeNil)
   132  			So(resp.Count, ShouldEqual, 0)
   133  			So(resp.Now, ShouldNotBeNil)
   134  		})
   135  
   136  		Convey("not ok; no pool ACL", func() {
   137  			tags := []string{"os:ubuntu|ubuntu22"}
   138  			one := createMockTaskResult(ctx, tags, apipb.TaskState_CANCELED, testTime)
   139  			two := createMockTaskResult(ctx, tags, apipb.TaskState_CANCELED, testTime.Add(time.Minute*10))
   140  			So(datastore.Put(ctx, one, two), ShouldBeNil)
   141  			req := &apipb.TasksCountRequest{
   142  				Start: timestamppb.New(testTime.Add(-10 * time.Hour)),
   143  				State: apipb.StateQuery_QUERY_ALL,
   144  				Tags:  []string{"pool:example-pool", "os:ubuntu15|ubuntu22"},
   145  			}
   146  			resp, err := srv.CountTasks(MockRequestState(ctx, state.SetCaller(identity.AnonymousIdentity)), req)
   147  			So(err, ShouldHaveGRPCStatus, codes.PermissionDenied)
   148  			So(err, ShouldErrLike, "the caller \"anonymous:anonymous\" doesn't have permission \"swarming.pools.listTasks\" in the pool \"example-pool\" or the pool doesn't exist")
   149  			So(resp, ShouldBeNil)
   150  		})
   151  
   152  		Convey("not ok; bad query filter", func() {
   153  			one := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime)
   154  			two := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime.Add(time.Minute*10))
   155  			So(datastore.Put(ctx, one, two), ShouldBeNil)
   156  			req := &apipb.TasksCountRequest{
   157  				Start: timestamppb.New(testTime.Add(-10 * time.Hour)),
   158  				State: apipb.StateQuery_QUERY_ALL,
   159  				End:   timestamppb.New(testTime.Add(-115 * time.Hour)),
   160  				Tags:  []string{"pool:example-pool"},
   161  			}
   162  			resp, err := srv.CountTasks(MockRequestState(ctx, state), req)
   163  			So(err, ShouldHaveGRPCStatus, codes.Internal)
   164  			So(err, ShouldErrLike, "datastore error counting tasks")
   165  			So(resp, ShouldBeNil)
   166  		})
   167  
   168  		Convey("not ok; start time before 2010", func() {
   169  			one := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime)
   170  			two := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime.Add(time.Minute*10))
   171  			So(datastore.Put(ctx, one, two), ShouldBeNil)
   172  			req := &apipb.TasksCountRequest{
   173  				Start: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
   174  				State: apipb.StateQuery_QUERY_ALL,
   175  				Tags:  []string{"pool:example-pool"},
   176  			}
   177  			resp, err := srv.CountTasks(MockRequestState(ctx, state), req)
   178  			So(err, ShouldHaveGRPCStatus, codes.InvalidArgument)
   179  			So(err, ShouldErrLike, "invalid time range")
   180  			So(resp, ShouldBeNil)
   181  		})
   182  
   183  		Convey("not ok; no start time", func() {
   184  			one := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime)
   185  			two := createMockTaskResult(ctx, nil, apipb.TaskState_CANCELED, testTime.Add(time.Minute*10))
   186  			So(datastore.Put(ctx, one, two), ShouldBeNil)
   187  			req := &apipb.TasksCountRequest{
   188  				State: apipb.StateQuery_QUERY_ALL,
   189  				Tags:  []string{"pool:example-pool"},
   190  			}
   191  			resp, err := srv.CountTasks(MockRequestState(ctx, state), req)
   192  			So(err, ShouldHaveGRPCStatus, codes.InvalidArgument)
   193  			So(err, ShouldErrLike, "start timestamp is required")
   194  			So(resp, ShouldBeNil)
   195  		})
   196  	})
   197  }