go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/rpcs/bots_list_bot_events_test.go (about) 1 // Copyright 2023 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 "fmt" 20 "testing" 21 "time" 22 23 "google.golang.org/grpc/codes" 24 "google.golang.org/protobuf/types/known/timestamppb" 25 26 "go.chromium.org/luci/auth/identity" 27 "go.chromium.org/luci/gae/impl/memory" 28 "go.chromium.org/luci/gae/service/datastore" 29 30 apipb "go.chromium.org/luci/swarming/proto/api_v2" 31 "go.chromium.org/luci/swarming/server/acls" 32 "go.chromium.org/luci/swarming/server/model" 33 34 . "github.com/smartystreets/goconvey/convey" 35 . "go.chromium.org/luci/common/testing/assertions" 36 ) 37 38 func TestListBotEvents(t *testing.T) { 39 t.Parallel() 40 41 state := NewMockedRequestState() 42 43 // A bot that should be visible. 44 state.MockBot("visible-bot", "visible-pool") 45 state.MockPool("visible-pool", "project:visible-realm") 46 state.MockPerm("project:visible-realm", acls.PermPoolsListBots) 47 48 // A bot the caller has no permissions over. 49 state.MockBot("hidden-bot", "hidden-pool") 50 state.MockPool("hidden-pool", "project:hidden-realm") 51 52 ctx := memory.Use(context.Background()) 53 datastore.GetTestable(ctx).AutoIndex(true) 54 datastore.GetTestable(ctx).Consistent(true) 55 56 testTime := time.Date(2023, time.January, 1, 2, 3, 4, 0, time.UTC) 57 58 total := 0 59 putEvent := func(botID string, ts time.Duration) { 60 total += 1 61 err := datastore.Put(ctx, &model.BotEvent{ 62 Key: datastore.NewKey(ctx, "BotEvent", "", int64(1000-total), model.BotRootKey(ctx, botID)), 63 Timestamp: testTime.Add(ts), 64 EventType: model.BotEventLog, 65 Message: fmt.Sprintf("at %s", ts), 66 Dimensions: []string{"a:1", "b:2"}, 67 BotCommon: model.BotCommon{ 68 State: []byte(`{"state": "1"}`), 69 ExternalIP: "1.2.3.4", 70 AuthenticatedAs: identity.Identity("bot:" + botID), 71 Version: "some-version", 72 Quarantined: false, 73 Maintenance: "maintenance msg", 74 TaskID: "task-id", 75 }, 76 }) 77 if err != nil { 78 panic(err) 79 } 80 } 81 82 expectedEvent := func(botID string, ts time.Duration) *apipb.BotEventResponse { 83 return &apipb.BotEventResponse{ 84 Ts: timestamppb.New(testTime.Add(ts)), 85 EventType: string(model.BotEventLog), 86 Message: fmt.Sprintf("at %s", ts), 87 Dimensions: []*apipb.StringListPair{ 88 {Key: "a", Value: []string{"1"}}, 89 {Key: "b", Value: []string{"2"}}, 90 }, 91 State: `{"state": "1"}`, 92 ExternalIp: "1.2.3.4", 93 AuthenticatedAs: "bot:" + botID, 94 Version: "some-version", 95 Quarantined: false, 96 MaintenanceMsg: "maintenance msg", 97 TaskId: "task-id", 98 } 99 } 100 101 for i := 0; i < 5; i++ { 102 putEvent("visible-bot", time.Hour*time.Duration(i)) 103 } 104 putEvent("hidden-bot", 0) 105 106 call := func(req *apipb.BotEventsRequest) (*apipb.BotEventsResponse, error) { 107 ctx := MockRequestState(ctx, state) 108 return (&BotsServer{}).ListBotEvents(ctx, req) 109 } 110 111 Convey("Bad bot ID", t, func() { 112 _, err := call(&apipb.BotEventsRequest{ 113 BotId: "", 114 Limit: 10, 115 }) 116 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 117 }) 118 119 Convey("Limit is checked", t, func() { 120 _, err := call(&apipb.BotEventsRequest{ 121 BotId: "doesnt-matter", 122 Limit: -10, 123 }) 124 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 125 _, err = call(&apipb.BotEventsRequest{ 126 BotId: "doesnt-matter", 127 Limit: 1001, 128 }) 129 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 130 }) 131 132 Convey("Cursor is checked", t, func() { 133 _, err := call(&apipb.BotEventsRequest{ 134 BotId: "doesnt-matter", 135 Cursor: "!!!!", 136 }) 137 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 138 }) 139 140 Convey("No permissions", t, func() { 141 _, err := call(&apipb.BotEventsRequest{ 142 BotId: "hidden-bot", 143 Limit: 10, 144 }) 145 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 146 }) 147 148 Convey("Bot not in a config", t, func() { 149 _, err := call(&apipb.BotEventsRequest{ 150 BotId: "unknown-bot", 151 Limit: 10, 152 }) 153 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 154 }) 155 156 Convey("All events", t, func() { 157 resp, err := call(&apipb.BotEventsRequest{ 158 BotId: "visible-bot", 159 }) 160 So(err, ShouldBeNil) 161 So(resp.Cursor, ShouldEqual, "") 162 So(resp.Items, ShouldResembleProto, []*apipb.BotEventResponse{ 163 expectedEvent("visible-bot", 4*time.Hour), 164 expectedEvent("visible-bot", 3*time.Hour), 165 expectedEvent("visible-bot", 2*time.Hour), 166 expectedEvent("visible-bot", 1*time.Hour), 167 expectedEvent("visible-bot", 0), 168 }) 169 }) 170 171 Convey("Pagination", t, func() { 172 resp, err := call(&apipb.BotEventsRequest{ 173 BotId: "visible-bot", 174 Limit: 3, 175 }) 176 So(err, ShouldBeNil) 177 So(resp.Cursor, ShouldNotEqual, "") 178 So(resp.Items, ShouldResembleProto, []*apipb.BotEventResponse{ 179 expectedEvent("visible-bot", 4*time.Hour), 180 expectedEvent("visible-bot", 3*time.Hour), 181 expectedEvent("visible-bot", 2*time.Hour), 182 }) 183 resp, err = call(&apipb.BotEventsRequest{ 184 BotId: "visible-bot", 185 Limit: 3, 186 Cursor: resp.Cursor, 187 }) 188 So(err, ShouldBeNil) 189 So(resp.Cursor, ShouldEqual, "") 190 So(resp.Items, ShouldResembleProto, []*apipb.BotEventResponse{ 191 expectedEvent("visible-bot", 1*time.Hour), 192 expectedEvent("visible-bot", 0), 193 }) 194 }) 195 196 Convey("Time range", t, func() { 197 resp, err := call(&apipb.BotEventsRequest{ 198 BotId: "visible-bot", 199 Start: timestamppb.New(testTime.Add(time.Hour)), // inclusive range 200 End: timestamppb.New(testTime.Add(4 * time.Hour)), // exclusive range 201 }) 202 So(err, ShouldBeNil) 203 So(resp.Cursor, ShouldEqual, "") 204 So(resp.Items, ShouldResembleProto, []*apipb.BotEventResponse{ 205 expectedEvent("visible-bot", 3*time.Hour), 206 expectedEvent("visible-bot", 2*time.Hour), 207 expectedEvent("visible-bot", 1*time.Hour), 208 }) 209 }) 210 }