go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/rpc/set_builder_health_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 rpc 16 17 import ( 18 "context" 19 "testing" 20 21 "google.golang.org/genproto/googleapis/rpc/status" 22 "google.golang.org/protobuf/types/known/emptypb" 23 24 "go.chromium.org/luci/gae/filter/txndefer" 25 "go.chromium.org/luci/gae/impl/memory" 26 "go.chromium.org/luci/gae/service/datastore" 27 "go.chromium.org/luci/server/auth" 28 "go.chromium.org/luci/server/auth/authtest" 29 30 "go.chromium.org/luci/buildbucket/appengine/model" 31 "go.chromium.org/luci/buildbucket/appengine/rpc/testutil" 32 "go.chromium.org/luci/buildbucket/bbperms" 33 pb "go.chromium.org/luci/buildbucket/proto" 34 35 . "github.com/smartystreets/goconvey/convey" 36 . "go.chromium.org/luci/common/testing/assertions" 37 ) 38 39 func TestValidateSetBuilderHealthRequest(t *testing.T) { 40 t.Parallel() 41 Convey("validateSetBuilderHealthRequest", t, func() { 42 ctx := memory.Use(context.Background()) 43 testutil.PutBucket(ctx, "project", "bucket", nil) 44 45 Convey("empty req", func() { 46 req := &pb.SetBuilderHealthRequest{} 47 err := validateRequest(ctx, req, nil, nil) 48 So(err, ShouldBeNil) 49 }) 50 51 Convey("ok req", func() { 52 ctx = auth.WithState(ctx, &authtest.FakeState{ 53 Identity: "user:someone@example.com", 54 IdentityPermissions: []authtest.RealmPermission{ 55 {Realm: "project:bucket", Permission: bbperms.BuildersSetHealth}, 56 {Realm: "builder:builder1", Permission: bbperms.BuildersSetHealth}, 57 {Realm: "builder:builder2", Permission: bbperms.BuildersSetHealth}, 58 {Realm: "builder:builder3", Permission: bbperms.BuildersSetHealth}, 59 }, 60 }) 61 req := &pb.SetBuilderHealthRequest{ 62 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 63 { 64 Id: &pb.BuilderID{ 65 Bucket: "bucket", 66 Project: "project", 67 Builder: "builder1", 68 }, 69 Health: &pb.HealthStatus{HealthScore: 10}, 70 }, 71 { 72 Id: &pb.BuilderID{ 73 Bucket: "bucket", 74 Project: "project", 75 Builder: "builder2", 76 }, 77 Health: &pb.HealthStatus{HealthScore: 0}, 78 }, 79 { 80 Id: &pb.BuilderID{ 81 Bucket: "bucket", 82 Project: "project", 83 Builder: "builder3", 84 }, 85 Health: &pb.HealthStatus{HealthScore: 4}, 86 }, 87 }, 88 } 89 resp := make([]*pb.SetBuilderHealthResponse_Response, 3) 90 err := validateRequest(ctx, req, nil, resp) 91 So(err, ShouldBeNil) 92 So(resp, ShouldResembleProto, []*pb.SetBuilderHealthResponse_Response{ 93 nil, nil, nil, 94 }) 95 }) 96 97 Convey("miltiple entries", func() { 98 ctx = auth.WithState(ctx, &authtest.FakeState{ 99 Identity: "user:someone@example.com", 100 IdentityPermissions: []authtest.RealmPermission{ 101 {Realm: "project:bucket", Permission: bbperms.BuildersSetHealth}, 102 {Realm: "builder:builder", Permission: bbperms.BuildersSetHealth}, 103 }, 104 }) 105 req := &pb.SetBuilderHealthRequest{ 106 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 107 { 108 Id: &pb.BuilderID{ 109 Bucket: "bucket", 110 Project: "project", 111 Builder: "builder", 112 }, 113 Health: &pb.HealthStatus{HealthScore: 10}, 114 }, 115 { 116 Id: &pb.BuilderID{ 117 Bucket: "bucket", 118 Project: "project", 119 Builder: "builder", 120 }, 121 Health: &pb.HealthStatus{HealthScore: 0}, 122 }, 123 { 124 Id: &pb.BuilderID{ 125 Bucket: "bucket", 126 Project: "project", 127 Builder: "builder", 128 }, 129 Health: &pb.HealthStatus{HealthScore: 4}, 130 }, 131 }, 132 } 133 resp := make([]*pb.SetBuilderHealthResponse_Response, 3) 134 err := validateRequest(ctx, req, nil, resp) 135 So(err, ShouldNotBeNil) 136 So(err.Error(), ShouldEqual, "The following builder has multiple entries: project/bucket/builder") 137 }) 138 139 Convey("bad health score", func() { 140 errs := map[int]error{} 141 ctx = auth.WithState(ctx, &authtest.FakeState{ 142 Identity: "user:someone@example.com", 143 IdentityPermissions: []authtest.RealmPermission{ 144 {Realm: "project:bucket", Permission: bbperms.BuildersSetHealth}, 145 {Realm: "builder:builder", Permission: bbperms.BuildersSetHealth}, 146 }, 147 }) 148 req := &pb.SetBuilderHealthRequest{ 149 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 150 { 151 Id: &pb.BuilderID{ 152 Bucket: "bucket", 153 Project: "project", 154 Builder: "builder", 155 }, 156 Health: &pb.HealthStatus{HealthScore: 11}, 157 }, 158 }, 159 } 160 resp := make([]*pb.SetBuilderHealthResponse_Response, 1) 161 err := validateRequest(ctx, req, errs, resp) 162 So(err, ShouldBeNil) 163 So(resp, ShouldResembleProto, []*pb.SetBuilderHealthResponse_Response{ 164 { 165 Response: &pb.SetBuilderHealthResponse_Response_Error{ 166 Error: &status.Status{ 167 Code: 3, 168 Message: "Builder: project/bucket/builder: HealthScore should be between 0 and 10", 169 }, 170 }, 171 }, 172 }) 173 }) 174 175 Convey("builderID not present, health is", func() { 176 errs := map[int]error{} 177 ctx = auth.WithState(ctx, &authtest.FakeState{ 178 Identity: "user:someone@example.com", 179 IdentityPermissions: []authtest.RealmPermission{ 180 {Realm: "project:bucket", Permission: bbperms.BuildersSetHealth}, 181 {Realm: "builder:builder", Permission: bbperms.BuildersSetHealth}, 182 }, 183 }) 184 req := &pb.SetBuilderHealthRequest{ 185 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 186 { 187 Health: &pb.HealthStatus{HealthScore: 11}, 188 }, 189 }, 190 } 191 err := validateRequest(ctx, req, errs, nil) 192 So(err, ShouldNotBeNil) 193 So(err.Error(), ShouldContainSubstring, ".health[0].id: required") 194 }) 195 }) 196 } 197 198 func TestSetBuilderHealth(t *testing.T) { 199 t.Parallel() 200 201 Convey("requests", t, func() { 202 ctx := memory.Use(context.Background()) 203 srv := &Builders{} 204 testutil.PutBucket(ctx, "chrome", "cq", nil) 205 testutil.PutBucket(ctx, "chromeos", "cq", nil) 206 207 Convey("bad request; no perms", func() { 208 req := &pb.SetBuilderHealthRequest{ 209 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 210 { 211 Id: &pb.BuilderID{ 212 Project: "project", 213 Bucket: "bucket", 214 Builder: "builder", 215 }, 216 Health: &pb.HealthStatus{HealthScore: 12}, 217 }, 218 { 219 Id: &pb.BuilderID{ 220 Project: "project2", 221 Bucket: "bucket2", 222 Builder: "builder2", 223 }, 224 Health: &pb.HealthStatus{HealthScore: 13}, 225 }, 226 }, 227 } 228 resp, err := srv.SetBuilderHealth(ctx, req) 229 So(err, ShouldBeNil) 230 So(resp, ShouldResembleProto, &pb.SetBuilderHealthResponse{ 231 Responses: []*pb.SetBuilderHealthResponse_Response{ 232 { 233 Response: &pb.SetBuilderHealthResponse_Response_Error{ 234 Error: &status.Status{ 235 Code: 7, 236 Message: "Builder: project/bucket/builder: attaching a status: rpc error: code = NotFound desc = requested resource not found or \"anonymous:anonymous\" does not have permission to view it", 237 }, 238 }, 239 }, 240 { 241 Response: &pb.SetBuilderHealthResponse_Response_Error{ 242 Error: &status.Status{ 243 Code: 7, 244 Message: "Builder: project2/bucket2/builder2: attaching a status: rpc error: code = NotFound desc = requested resource not found or \"anonymous:anonymous\" does not have permission to view it", 245 }, 246 }, 247 }, 248 }, 249 }) 250 }) 251 252 Convey("bad request; has perms", func() { 253 So(datastore.Put(ctx, &model.Builder{ 254 ID: "amd-cq", 255 Parent: model.BucketKey(ctx, "chrome", "cq"), 256 }), ShouldBeNil) 257 ctx = auth.WithState(ctx, &authtest.FakeState{ 258 Identity: "user:someone@example.com", 259 IdentityPermissions: []authtest.RealmPermission{ 260 {Realm: "chrome:cq", Permission: bbperms.BuildersSetHealth}, 261 {Realm: "builder:amd-cq", Permission: bbperms.BuildersSetHealth}, 262 }, 263 }) 264 req := &pb.SetBuilderHealthRequest{ 265 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 266 { 267 Id: &pb.BuilderID{ 268 Project: "chrome", 269 Bucket: "cq", 270 Builder: "amd-cq", 271 }, 272 Health: &pb.HealthStatus{HealthScore: 12}, 273 }, 274 { 275 Id: &pb.BuilderID{}, 276 Health: &pb.HealthStatus{HealthScore: 13}, 277 }, 278 }, 279 } 280 _, err := srv.SetBuilderHealth(ctx, req) 281 So(err.Error(), ShouldContainSubstring, ".health[1].id.project: required (and 2 other errors)") 282 }) 283 284 Convey("bad req; one no perm, one validation err, one no builder saved", func() { 285 So(datastore.Put(ctx, &model.Builder{ 286 ID: "amd-cq", 287 Parent: model.BucketKey(ctx, "chrome", "cq"), 288 }), ShouldBeNil) 289 So(datastore.Put(ctx, &model.Builder{ 290 ID: "amd-cq", 291 Parent: model.BucketKey(ctx, "chromeos", "cq"), 292 }), ShouldBeNil) 293 ctx = auth.WithState(ctx, &authtest.FakeState{ 294 Identity: "user:someone@example.com", 295 IdentityPermissions: []authtest.RealmPermission{ 296 {Realm: "chrome:cq", Permission: bbperms.BuildersSetHealth}, 297 {Realm: "builder:amd-cq", Permission: bbperms.BuildersSetHealth}, 298 {Realm: "builder:amd-cq-2", Permission: bbperms.BuildersSetHealth}, 299 }, 300 }) 301 req := &pb.SetBuilderHealthRequest{ 302 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 303 { 304 Id: &pb.BuilderID{ 305 Project: "chrome", 306 Bucket: "cq", 307 Builder: "amd-cq", 308 }, 309 Health: &pb.HealthStatus{HealthScore: 12}, 310 }, 311 { 312 Id: &pb.BuilderID{ 313 Project: "chrome", 314 Bucket: "cq", 315 Builder: "amd-cq-2", 316 }, 317 Health: &pb.HealthStatus{HealthScore: 8}, 318 }, 319 { 320 Id: &pb.BuilderID{ 321 Project: "chromeos", 322 Bucket: "cq", 323 Builder: "amd-cq", 324 }, 325 Health: &pb.HealthStatus{HealthScore: 12}, 326 }, 327 }, 328 } 329 resp, err := srv.SetBuilderHealth(ctx, req) 330 So(err, ShouldBeNil) 331 So(resp, ShouldResembleProto, &pb.SetBuilderHealthResponse{ 332 Responses: []*pb.SetBuilderHealthResponse_Response{ 333 { 334 Response: &pb.SetBuilderHealthResponse_Response_Error{ 335 Error: &status.Status{ 336 Message: "Builder: chrome/cq/amd-cq: HealthScore should be between 0 and 10", 337 Code: 3, 338 }, 339 }, 340 }, 341 { 342 Response: &pb.SetBuilderHealthResponse_Response_Error{ 343 Error: &status.Status{ 344 Message: "attaching a status: rpc error: code = Internal desc = failed to get builder amd-cq-2: datastore: no such entity", 345 Code: 13, 346 }, 347 }, 348 }, 349 { 350 Response: &pb.SetBuilderHealthResponse_Response_Error{ 351 Error: &status.Status{ 352 Message: "Builder: chromeos/cq/amd-cq: attaching a status: rpc error: code = NotFound desc = requested resource not found or \"user:someone@example.com\" does not have permission to view it", 353 Code: 7, 354 }, 355 }, 356 }, 357 }, 358 }) 359 }) 360 }) 361 362 Convey("existing entities", t, func() { 363 ctx := memory.UseWithAppID(context.Background(), "fake-cr-buildbucket") 364 datastore.GetTestable(ctx).AutoIndex(true) 365 datastore.GetTestable(ctx).Consistent(true) 366 ctx = txndefer.FilterRDS(ctx) 367 srv := &Builders{} 368 testutil.PutBucket(ctx, "chrome", "cq", nil) 369 370 Convey("builders exists; update is normal", func() { 371 bktKey := model.BucketKey(ctx, "chrome", "cq") 372 373 bldrToPut1 := &model.Builder{ 374 ID: "amd-cq", 375 Parent: bktKey, 376 Config: &pb.BuilderConfig{ 377 BuilderHealthMetricsLinks: &pb.BuilderConfig_BuilderHealthLinks{ 378 DataLinks: map[string]string{ 379 "user": "data-link-for-amd-cq", 380 }, 381 DocLinks: map[string]string{ 382 "user": "doc-link-for-amd-cq", 383 }, 384 }, 385 }, 386 } 387 bldrToPut2 := &model.Builder{ 388 ID: "amd-cq-2", 389 Parent: bktKey, 390 Config: &pb.BuilderConfig{ 391 BuilderHealthMetricsLinks: &pb.BuilderConfig_BuilderHealthLinks{ 392 DataLinks: map[string]string{ 393 "user": "data-link-for-amd-cq-2", 394 }, 395 DocLinks: map[string]string{ 396 "user": "doc-link-for-amd-cq-2", 397 }, 398 }, 399 }, 400 } 401 bldrToPut3 := &model.Builder{ 402 ID: "amd-cq-3", 403 Parent: bktKey, 404 } 405 So(datastore.Put(ctx, bldrToPut1, bldrToPut2, bldrToPut3), ShouldBeNil) 406 ctx = auth.WithState(ctx, &authtest.FakeState{ 407 Identity: "user:someone@example.com", 408 IdentityPermissions: []authtest.RealmPermission{ 409 {Realm: "chrome:cq", Permission: bbperms.BuildersSetHealth}, 410 {Realm: "builder:amd-cq", Permission: bbperms.BuildersSetHealth}, 411 {Realm: "builder:amd-cq-2", Permission: bbperms.BuildersSetHealth}, 412 {Realm: "builder:amd-cq-3", Permission: bbperms.BuildersSetHealth}, 413 }, 414 }) 415 req := &pb.SetBuilderHealthRequest{ 416 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 417 { 418 Id: &pb.BuilderID{ 419 Project: "chrome", 420 Bucket: "cq", 421 Builder: "amd-cq", 422 }, 423 Health: &pb.HealthStatus{ 424 HealthScore: 9, 425 DataLinks: map[string]string{ 426 "user": "data-link-for-amd-cq-from-req", 427 }, 428 DocLinks: map[string]string{ 429 "user": "doc-link-for-amd-cq-from-req", 430 }, 431 }, 432 }, 433 { 434 Id: &pb.BuilderID{ 435 Project: "chrome", 436 Bucket: "cq", 437 Builder: "amd-cq-2", 438 }, 439 Health: &pb.HealthStatus{ 440 HealthScore: 8, 441 }, 442 }, 443 { 444 Id: &pb.BuilderID{ 445 Project: "chrome", 446 Bucket: "cq", 447 Builder: "amd-cq-3", 448 }, 449 Health: &pb.HealthStatus{ 450 HealthScore: 2, 451 }, 452 }, 453 }, 454 } 455 _, err := srv.SetBuilderHealth(ctx, req) 456 So(err, ShouldBeNil) 457 expectedBuilder1 := &model.Builder{ID: "amd-cq", Parent: bktKey} 458 expectedBuilder2 := &model.Builder{ID: "amd-cq-2", Parent: bktKey} 459 expectedBuilder3 := &model.Builder{ID: "amd-cq-3", Parent: bktKey} 460 So(datastore.Get(ctx, expectedBuilder1, expectedBuilder2, expectedBuilder3), ShouldBeNil) 461 So(expectedBuilder1.Metadata.Health.HealthScore, ShouldEqual, 9) 462 So(expectedBuilder1.Metadata.Health.Reporter, ShouldEqual, "someone@example.com") 463 So(expectedBuilder1.Metadata.Health.ReportedTime, ShouldNotBeNil) 464 So(expectedBuilder1.Metadata.Health.DataLinks, ShouldResemble, map[string]string{ 465 "user": "data-link-for-amd-cq-from-req", 466 }) 467 So(expectedBuilder1.Metadata.Health.DocLinks, ShouldResemble, map[string]string{ 468 "user": "doc-link-for-amd-cq-from-req", 469 }) 470 So(expectedBuilder2.Metadata.Health.HealthScore, ShouldEqual, 8) 471 So(expectedBuilder2.Metadata.Health.Reporter, ShouldEqual, "someone@example.com") 472 So(expectedBuilder2.Metadata.Health.ReportedTime, ShouldNotBeNil) 473 So(expectedBuilder2.Metadata.Health.DataLinks, ShouldResemble, map[string]string{ 474 "user": "data-link-for-amd-cq-2", 475 }) 476 So(expectedBuilder2.Metadata.Health.DocLinks, ShouldResemble, map[string]string{ 477 "user": "doc-link-for-amd-cq-2", 478 }) 479 So(expectedBuilder3.Metadata.Health.HealthScore, ShouldEqual, 2) 480 So(expectedBuilder3.Metadata.Health.Reporter, ShouldEqual, "someone@example.com") 481 So(expectedBuilder3.Metadata.Health.ReportedTime, ShouldNotBeNil) 482 }) 483 484 Convey("one builder does not exist", func() { 485 bktKey := model.BucketKey(ctx, "chrome", "cq") 486 So(datastore.Put(ctx, &model.Builder{ 487 ID: "amd-cq", 488 Parent: bktKey, 489 }), ShouldBeNil) 490 ctx = auth.WithState(ctx, &authtest.FakeState{ 491 Identity: "user:someone@example.com", 492 IdentityPermissions: []authtest.RealmPermission{ 493 {Realm: "chrome:cq", Permission: bbperms.BuildersSetHealth}, 494 {Realm: "builder:amd-cq", Permission: bbperms.BuildersSetHealth}, 495 }, 496 }) 497 req := &pb.SetBuilderHealthRequest{ 498 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 499 { 500 Id: &pb.BuilderID{ 501 Project: "chrome", 502 Bucket: "cq", 503 Builder: "amd-cq", 504 }, 505 Health: &pb.HealthStatus{ 506 HealthScore: 9, 507 }, 508 }, 509 { 510 Id: &pb.BuilderID{ 511 Project: "chrome", 512 Bucket: "cq", 513 Builder: "amd-cq-2", 514 }, 515 Health: &pb.HealthStatus{ 516 HealthScore: 8, 517 }, 518 }, 519 }, 520 } 521 resp, err := srv.SetBuilderHealth(ctx, req) 522 So(err, ShouldBeNil) 523 So(resp, ShouldResembleProto, &pb.SetBuilderHealthResponse{ 524 Responses: []*pb.SetBuilderHealthResponse_Response{ 525 { 526 Response: &pb.SetBuilderHealthResponse_Response_Result{ 527 Result: &emptypb.Empty{}, 528 }, 529 }, 530 { 531 Response: &pb.SetBuilderHealthResponse_Response_Error{ 532 Error: &status.Status{ 533 Code: 13, 534 Message: "attaching a status: rpc error: code = Internal desc = failed to get builder amd-cq-2: datastore: no such entity", 535 }, 536 }, 537 }, 538 }, 539 }) 540 expectedBuilder1 := &model.Builder{ID: "amd-cq", Parent: bktKey} 541 So(datastore.Get(ctx, expectedBuilder1), ShouldBeNil) 542 So(expectedBuilder1.Metadata.Health.HealthScore, ShouldEqual, 9) 543 }) 544 545 Convey("multiple requests for same builder", func() { 546 So(datastore.Put(ctx, &model.Builder{ 547 ID: "amd-cq", 548 Parent: model.BucketKey(ctx, "chrome", "cq"), 549 }), ShouldBeNil) 550 ctx = auth.WithState(ctx, &authtest.FakeState{ 551 Identity: "user:someone@example.com", 552 IdentityPermissions: []authtest.RealmPermission{ 553 {Realm: "chrome:cq", Permission: bbperms.BuildersSetHealth}, 554 {Realm: "builder:amd-cq", Permission: bbperms.BuildersSetHealth}, 555 }, 556 }) 557 req := &pb.SetBuilderHealthRequest{ 558 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 559 { 560 Id: &pb.BuilderID{ 561 Project: "chrome", 562 Bucket: "cq", 563 Builder: "amd-cq", 564 }, 565 Health: &pb.HealthStatus{HealthScore: 12}, 566 }, 567 { 568 Id: &pb.BuilderID{ 569 Project: "chrome", 570 Bucket: "cq", 571 Builder: "amd-cq", 572 }, 573 Health: &pb.HealthStatus{HealthScore: 13}, 574 }, 575 }, 576 } 577 _, err := srv.SetBuilderHealth(ctx, req) 578 So(err.Error(), ShouldContainSubstring, "The following builder has multiple entries: chrome/cq/amd-cq") 579 }) 580 }) 581 582 Convey("links", t, func() { 583 ctx := memory.UseWithAppID(context.Background(), "fake-cr-buildbucket") 584 datastore.GetTestable(ctx).AutoIndex(true) 585 datastore.GetTestable(ctx).Consistent(true) 586 ctx = txndefer.FilterRDS(ctx) 587 srv := &Builders{} 588 testutil.PutBucket(ctx, "chrome", "cq", nil) 589 bktKey := model.BucketKey(ctx, "chrome", "cq") 590 So(datastore.Put(ctx, &model.Builder{ 591 ID: "amd-cq", 592 Parent: bktKey, 593 Config: &pb.BuilderConfig{ 594 BuilderHealthMetricsLinks: &pb.BuilderConfig_BuilderHealthLinks{ 595 DataLinks: map[string]string{ 596 "google.com": "go/somelink", 597 "chromium.org": "some_public_link.com", 598 }, 599 DocLinks: map[string]string{ 600 "google.com": "go/some_doc_link", 601 "chromium.org": "some_public_doc_link.com", 602 }, 603 }, 604 }, 605 }), ShouldBeNil) 606 607 Convey("links from cfg", func() { 608 ctx = auth.WithState(ctx, &authtest.FakeState{ 609 Identity: "user:someone@google.com", 610 IdentityPermissions: []authtest.RealmPermission{ 611 {Realm: "chrome:cq", Permission: bbperms.BuildersSetHealth}, 612 {Realm: "builder:amd-cq", Permission: bbperms.BuildersSetHealth}, 613 }, 614 }) 615 req := &pb.SetBuilderHealthRequest{ 616 Health: []*pb.SetBuilderHealthRequest_BuilderHealth{ 617 { 618 Id: &pb.BuilderID{ 619 Project: "chrome", 620 Bucket: "cq", 621 Builder: "amd-cq", 622 }, 623 Health: &pb.HealthStatus{ 624 HealthScore: 9, 625 }, 626 }, 627 }, 628 } 629 _, err := srv.SetBuilderHealth(ctx, req) 630 So(err, ShouldBeNil) 631 expectedBuilder := &model.Builder{ID: "amd-cq", Parent: bktKey} 632 So(datastore.Get(ctx, expectedBuilder), ShouldBeNil) 633 So(expectedBuilder.Metadata.Health.DataLinks, ShouldResemble, map[string]string{ 634 "google.com": "go/somelink", 635 "chromium.org": "some_public_link.com", 636 }) 637 So(expectedBuilder.Metadata.Health.DocLinks, ShouldResemble, map[string]string{ 638 "google.com": "go/some_doc_link", 639 "chromium.org": "some_public_doc_link.com", 640 }) 641 }) 642 }) 643 }