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 }