go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/gerrit/gerritfake/fake_test.go (about) 1 // Copyright 2020 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 gerritfake 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "testing" 22 "time" 23 24 "google.golang.org/grpc/codes" 25 "google.golang.org/protobuf/types/known/timestamppb" 26 27 gerritutil "go.chromium.org/luci/common/api/gerrit" 28 "go.chromium.org/luci/common/clock/testclock" 29 gerritpb "go.chromium.org/luci/common/proto/gerrit" 30 "go.chromium.org/luci/grpc/grpcutil" 31 32 "go.chromium.org/luci/cv/internal/gerrit" 33 34 . "github.com/smartystreets/goconvey/convey" 35 . "go.chromium.org/luci/common/testing/assertions" 36 ) 37 38 func TestRelationship(t *testing.T) { 39 t.Parallel() 40 41 Convey("Relationship works", t, func() { 42 ci1 := CI(1, PS(1), AllRevs()) 43 ci2 := CI(2, PS(2), AllRevs()) 44 ci3 := CI(3, PS(3), AllRevs()) 45 ci4 := CI(4, PS(4), AllRevs()) 46 f := WithCIs("host", ACLRestricted("infra"), ci1, ci2, ci3, ci4) 47 // Diamond using latest patchsets. 48 // --<-- 2_2 --<-- 49 // / \ 50 // 1_1 4_4 51 // \ / 52 // --<-- 3-3 --<-- 53 f.SetDependsOn("host", ci4, ci3, ci2) // 2 parents. 54 f.SetDependsOn("host", ci3, ci1) 55 f.SetDependsOn("host", ci2, ci1) 56 57 // Chain made by prior patchsets. 58 // 2_1 --<-- 3_2 --<-- 4_3 59 f.SetDependsOn("host", "4_3", "3_2") 60 f.SetDependsOn("host", "3_2", "2_1") 61 ctx := context.Background() 62 63 Convey("with allowed project", func() { 64 gc, err := f.MakeClient(ctx, "host", "infra") 65 So(err, ShouldBeNil) 66 67 Convey("No relations", func() { 68 resp, err := gc.GetRelatedChanges(ctx, &gerritpb.GetRelatedChangesRequest{ 69 Number: 4, 70 Project: "infra/infra", 71 RevisionId: "1", 72 }) 73 So(err, ShouldBeNil) 74 So(resp, ShouldResembleProto, &gerritpb.GetRelatedChangesResponse{}) 75 }) 76 77 Convey("Descendants only", func() { 78 resp, err := gc.GetRelatedChanges(ctx, &gerritpb.GetRelatedChangesRequest{ 79 Number: 2, 80 Project: "infra/infra", 81 RevisionId: "1", 82 }) 83 So(err, ShouldBeNil) 84 sortRelated(resp) 85 So(resp, ShouldResembleProto, &gerritpb.GetRelatedChangesResponse{ 86 Changes: []*gerritpb.GetRelatedChangesResponse_ChangeAndCommit{ 87 { 88 Project: "infra/infra", 89 Commit: &gerritpb.CommitInfo{ 90 Id: "rev-000002-001", 91 Parents: []*gerritpb.CommitInfo_Parent{{Id: "fake_parent_commit"}}, 92 }, 93 Number: 2, 94 Patchset: 1, 95 CurrentPatchset: 2, 96 }, 97 { 98 Project: "infra/infra", 99 Commit: &gerritpb.CommitInfo{ 100 Id: "rev-000003-002", 101 Parents: []*gerritpb.CommitInfo_Parent{{Id: "rev-000002-001"}}, 102 }, 103 Number: 3, 104 Patchset: 2, 105 CurrentPatchset: 3, 106 }, 107 { 108 Project: "infra/infra", 109 Commit: &gerritpb.CommitInfo{ 110 Id: "rev-000004-003", 111 Parents: []*gerritpb.CommitInfo_Parent{{Id: "rev-000003-002"}}, 112 }, 113 Number: 4, 114 Patchset: 3, 115 CurrentPatchset: 4, 116 }, 117 }, 118 }) 119 }) 120 121 Convey("Diamond", func() { 122 resp, err := gc.GetRelatedChanges(ctx, &gerritpb.GetRelatedChangesRequest{ 123 Number: 4, 124 RevisionId: "4", 125 }) 126 So(err, ShouldBeNil) 127 sortRelated(resp) 128 So(resp, ShouldResembleProto, &gerritpb.GetRelatedChangesResponse{ 129 Changes: []*gerritpb.GetRelatedChangesResponse_ChangeAndCommit{ 130 { 131 Project: "infra/infra", 132 Commit: &gerritpb.CommitInfo{ 133 Id: "rev-000001-001", 134 Parents: []*gerritpb.CommitInfo_Parent{{Id: "fake_parent_commit"}}, 135 }, 136 Number: 1, 137 Patchset: 1, 138 CurrentPatchset: 1, 139 }, 140 { 141 Project: "infra/infra", 142 Commit: &gerritpb.CommitInfo{ 143 Id: "rev-000002-002", 144 Parents: []*gerritpb.CommitInfo_Parent{{Id: "rev-000001-001"}}, 145 }, 146 Number: 2, 147 Patchset: 2, 148 CurrentPatchset: 2, 149 }, 150 { 151 Project: "infra/infra", 152 Commit: &gerritpb.CommitInfo{ 153 Id: "rev-000003-003", 154 Parents: []*gerritpb.CommitInfo_Parent{{Id: "rev-000001-001"}}, 155 }, 156 Number: 3, 157 Patchset: 3, 158 CurrentPatchset: 3, 159 }, 160 { 161 Project: "infra/infra", 162 Commit: &gerritpb.CommitInfo{ 163 Id: "rev-000004-004", 164 Parents: []*gerritpb.CommitInfo_Parent{ 165 {Id: "rev-000003-003"}, 166 {Id: "rev-000002-002"}, 167 }, 168 }, 169 Number: 4, 170 Patchset: 4, 171 CurrentPatchset: 4, 172 }, 173 }, 174 }) 175 }) 176 177 Convey("Part of Diamond", func() { 178 resp, err := gc.GetRelatedChanges(ctx, &gerritpb.GetRelatedChangesRequest{ 179 Number: 3, 180 RevisionId: "3", 181 }) 182 So(err, ShouldBeNil) 183 sortRelated(resp) 184 So(resp, ShouldResembleProto, &gerritpb.GetRelatedChangesResponse{ 185 Changes: []*gerritpb.GetRelatedChangesResponse_ChangeAndCommit{ 186 { 187 Project: "infra/infra", 188 Commit: &gerritpb.CommitInfo{ 189 Id: "rev-000001-001", 190 Parents: []*gerritpb.CommitInfo_Parent{{Id: "fake_parent_commit"}}, 191 }, 192 Number: 1, 193 Patchset: 1, 194 CurrentPatchset: 1, 195 }, 196 { 197 Project: "infra/infra", 198 Commit: &gerritpb.CommitInfo{ 199 Id: "rev-000003-003", 200 Parents: []*gerritpb.CommitInfo_Parent{{Id: "rev-000001-001"}}, 201 }, 202 Number: 3, 203 Patchset: 3, 204 CurrentPatchset: 3, 205 }, 206 { 207 Project: "infra/infra", 208 Commit: &gerritpb.CommitInfo{ 209 Id: "rev-000004-004", 210 Parents: []*gerritpb.CommitInfo_Parent{ 211 {Id: "rev-000003-003"}, 212 {Id: "rev-000002-002"}, 213 }, 214 }, 215 Number: 4, 216 Patchset: 4, 217 CurrentPatchset: 4, 218 }, 219 }, 220 }) 221 }) 222 }) 223 224 Convey("with disallowed project", func() { 225 gc, err := f.MakeClient(ctx, "host", "spying-luci-project") 226 So(err, ShouldBeNil) 227 _, err = gc.GetRelatedChanges(ctx, &gerritpb.GetRelatedChangesRequest{ 228 Number: 4, 229 RevisionId: "1", 230 }) 231 So(err, ShouldNotBeNil) 232 So(grpcutil.Code(err), ShouldEqual, codes.NotFound) 233 }) 234 }) 235 } 236 237 // sortRelated ensures deterministic yet ultimately abitrary order. 238 func sortRelated(r *gerritpb.GetRelatedChangesResponse) { 239 key := func(i int) string { 240 c := r.GetChanges()[i] 241 return fmt.Sprintf("%40s:%020d:%020d", c.GetCommit().GetId(), c.GetNumber(), c.GetPatchset()) 242 } 243 sort.Slice(r.GetChanges(), func(i, j int) bool { return key(i) < key(j) }) 244 } 245 246 func TestFiles(t *testing.T) { 247 t.Parallel() 248 249 Convey("Files' handling works", t, func() { 250 sortedFiles := func(r *gerritpb.ListFilesResponse) []string { 251 fs := make([]string, 0, len(r.GetFiles())) 252 for f := range r.GetFiles() { 253 fs = append(fs, f) 254 } 255 sort.Strings(fs) 256 return fs 257 } 258 ciDefault := CI(1) 259 ciCustom := CI(2, Files("ps1/cus.tom", "bl.ah"), PS(2), Files("still/custom")) 260 ciNoFiles := CI(3, Files()) 261 f := WithCIs("host", ACLRestricted("infra"), ciDefault, ciCustom, ciNoFiles) 262 263 ctx := context.Background() 264 gc, err := f.MakeClient(ctx, "host", "infra") 265 So(err, ShouldBeNil) 266 267 Convey("change or revision NotFound", func() { 268 _, err := gc.ListFiles(ctx, &gerritpb.ListFilesRequest{Number: 123213, RevisionId: "1"}) 269 So(grpcutil.Code(err), ShouldEqual, codes.NotFound) 270 _, err = gc.ListFiles(ctx, &gerritpb.ListFilesRequest{ 271 Number: ciDefault.GetNumber(), 272 RevisionId: "not existing", 273 }) 274 So(grpcutil.Code(err), ShouldEqual, codes.NotFound) 275 }) 276 277 Convey("Default", func() { 278 resp, err := gc.ListFiles(ctx, &gerritpb.ListFilesRequest{ 279 Number: ciDefault.GetNumber(), 280 RevisionId: ciDefault.GetCurrentRevision(), 281 }) 282 So(err, ShouldBeNil) 283 So(sortedFiles(resp), ShouldResemble, []string{"ps001/c.cpp", "shared/s.py"}) 284 }) 285 286 Convey("Custom", func() { 287 resp, err := gc.ListFiles(ctx, &gerritpb.ListFilesRequest{ 288 Number: ciCustom.GetNumber(), 289 RevisionId: "1", 290 }) 291 So(err, ShouldBeNil) 292 So(sortedFiles(resp), ShouldResemble, []string{"bl.ah", "ps1/cus.tom"}) 293 resp, err = gc.ListFiles(ctx, &gerritpb.ListFilesRequest{ 294 Number: ciCustom.GetNumber(), 295 RevisionId: "2", 296 }) 297 So(err, ShouldBeNil) 298 So(sortedFiles(resp), ShouldResemble, []string{"still/custom"}) 299 }) 300 301 Convey("NoFiles", func() { 302 resp, err := gc.ListFiles(ctx, &gerritpb.ListFilesRequest{ 303 Number: ciNoFiles.GetNumber(), 304 RevisionId: ciNoFiles.GetCurrentRevision(), 305 }) 306 So(err, ShouldBeNil) 307 So(resp.GetFiles(), ShouldHaveLength, 0) 308 }) 309 }) 310 } 311 312 func TestGetChange(t *testing.T) { 313 t.Parallel() 314 315 Convey("GetChange handling works", t, func() { 316 ci := CI(100100, PS(4), AllRevs()) 317 So(ci.GetRevisions(), ShouldHaveLength, 4) 318 f := WithCIs("host", ACLRestricted("infra"), ci) 319 320 ctx := context.Background() 321 gc, err := f.MakeClient(ctx, "host", "infra") 322 So(err, ShouldBeNil) 323 324 Convey("NotFound", func() { 325 _, err := gc.GetChange(ctx, &gerritpb.GetChangeRequest{Number: 12321}) 326 So(grpcutil.Code(err), ShouldEqual, codes.NotFound) 327 }) 328 329 Convey("Default", func() { 330 resp, err := gc.GetChange(ctx, &gerritpb.GetChangeRequest{Number: 100100}) 331 So(err, ShouldBeNil) 332 So(resp.GetCurrentRevision(), ShouldEqual, "") 333 So(resp.GetRevisions(), ShouldHaveLength, 0) 334 So(resp.GetLabels(), ShouldHaveLength, 0) 335 }) 336 337 Convey("CURRENT_REVISION", func() { 338 resp, err := gc.GetChange(ctx, &gerritpb.GetChangeRequest{ 339 Number: 100100, 340 Options: []gerritpb.QueryOption{gerritpb.QueryOption_CURRENT_REVISION}}) 341 So(err, ShouldBeNil) 342 So(resp.GetRevisions(), ShouldHaveLength, 1) 343 So(resp.GetRevisions()[resp.GetCurrentRevision()], ShouldNotBeNil) 344 }) 345 346 Convey("Full", func() { 347 resp, err := gc.GetChange(ctx, &gerritpb.GetChangeRequest{ 348 Number: 100100, 349 Options: []gerritpb.QueryOption{ 350 gerritpb.QueryOption_ALL_REVISIONS, 351 gerritpb.QueryOption_DETAILED_ACCOUNTS, 352 gerritpb.QueryOption_DETAILED_LABELS, 353 gerritpb.QueryOption_SKIP_MERGEABLE, 354 gerritpb.QueryOption_MESSAGES, 355 gerritpb.QueryOption_SUBMITTABLE, 356 }}) 357 So(err, ShouldBeNil) 358 So(resp, ShouldResembleProto, ci) 359 }) 360 }) 361 } 362 363 func TestListChanges(t *testing.T) { 364 t.Parallel() 365 366 Convey("ListChanges works", t, func() { 367 f := WithCIs("empty", ACLRestricted("empty")) 368 ctx := context.Background() 369 370 mustCurrentClient := func(host, luciProject string) gerrit.Client { 371 cl, err := f.MakeClient(ctx, host, luciProject) 372 So(err, ShouldBeNil) 373 return cl 374 } 375 376 listChangeIDs := func(client gerrit.Client, req *gerritpb.ListChangesRequest) []int { 377 out, err := client.ListChanges(ctx, req) 378 So(err, ShouldBeNil) 379 So(out.GetMoreChanges(), ShouldBeFalse) 380 ids := make([]int, len(out.GetChanges())) 381 for i, ch := range out.GetChanges() { 382 ids[i] = int(ch.GetNumber()) 383 if i > 0 { 384 // Ensure monotonically non-decreasing update timestamps. 385 prior := out.GetChanges()[i-1] 386 So(prior.GetUpdated().AsTime().Before(ch.GetUpdated().AsTime()), ShouldBeFalse) 387 } 388 } 389 return ids 390 } 391 392 f.AddFrom(WithCIs("chrome-internal", ACLRestricted("infra-internal"), 393 CI(9001, Project("infra/infra-internal")), 394 CI(9002, Project("infra/infra-internal")), 395 )) 396 397 Convey("ACLs enforced", func() { 398 So(listChangeIDs(mustCurrentClient("chrome-internal", "spy"), 399 &gerritpb.ListChangesRequest{}), ShouldResemble, []int{}) 400 So(listChangeIDs(mustCurrentClient("chrome-internal", "infra-internal"), 401 &gerritpb.ListChangesRequest{}), ShouldResemble, []int{9002, 9001}) 402 }) 403 404 var epoch = time.Date(2011, time.February, 3, 4, 5, 6, 7, time.UTC) 405 u0 := Updated(epoch) 406 u1 := Updated(epoch.Add(time.Minute)) 407 u2 := Updated(epoch.Add(2 * time.Minute)) 408 f.AddFrom(WithCIs("chromium", ACLPublic(), 409 CI(8001, u1, Project("infra/infra"), CQ(+2)), 410 CI(8002, u2, Project("infra/luci/luci-go"), Vote("Commit-Queue", +1), Vote("Code-Review", -1)), 411 CI(8003, u0, Project("infra/luci/luci-go"), Status("MERGED"), Vote("Code-Review", +1)), 412 )) 413 414 Convey("Order and limit", func() { 415 g := mustCurrentClient("chromium", "anyone") 416 So(listChangeIDs(g, &gerritpb.ListChangesRequest{}), ShouldResemble, []int{8002, 8001, 8003}) 417 418 out, err := g.ListChanges(ctx, &gerritpb.ListChangesRequest{Limit: 2}) 419 So(err, ShouldBeNil) 420 So(out.GetMoreChanges(), ShouldBeTrue) 421 So(out.GetChanges()[0].GetNumber(), ShouldEqual, 8002) 422 So(out.GetChanges()[1].GetNumber(), ShouldEqual, 8001) 423 }) 424 425 Convey("Filtering works", func() { 426 query := func(q string) []int { 427 return listChangeIDs(mustCurrentClient("chromium", "anyone"), 428 &gerritpb.ListChangesRequest{Query: q}) 429 } 430 Convey("before/after", func() { 431 So(gerritutil.FormatTime(epoch), ShouldResemble, `"2011-02-03 04:05:06.000000007"`) 432 So(query(`before:"2011-02-03 04:05:06.000000006"`), ShouldResemble, []int{}) 433 // 1 ns later 434 So(query(`before:"2011-02-03 04:05:06.000000007"`), ShouldResemble, []int{8003}) 435 So(query(` after:"2011-02-03 04:05:06.000000007"`), ShouldResemble, []int{8002, 8001, 8003}) 436 // 1 minute later 437 So(query(` after:"2011-02-03 04:06:06.000000007"`), ShouldResemble, []int{8002, 8001}) 438 // 1 minute later 439 So(query(` after:"2011-02-03 04:07:06.000000007"`), ShouldResemble, []int{8002}) 440 // Surround middle CL: 441 So(query(``+ 442 ` after:"2011-02-03 04:05:30.000000000" `+ 443 `before:"2011-02-03 04:06:30.000000000"`), ShouldResemble, []int{8001}) 444 }) 445 Convey("Project prefix", func() { 446 So(query(`projects:"inf"`), ShouldResemble, []int{8002, 8001, 8003}) 447 So(query(`projects:"infra/"`), ShouldResemble, []int{8002, 8001, 8003}) 448 So(query(`projects:"infra/luci"`), ShouldResemble, []int{8002, 8003}) 449 So(query(`projects:"typo"`), ShouldResemble, []int{}) 450 }) 451 Convey("Project exact", func() { 452 So(query(`project:"infra/infra"`), ShouldResemble, []int{8001}) 453 So(query(`project:"infra"`), ShouldResemble, []int{}) 454 So(query(`(project:"infra/infra" OR project:"infra/luci/luci-go")`), ShouldResemble, 455 []int{8002, 8001, 8003}) 456 }) 457 Convey("Status", func() { 458 So(query(`status:new`), ShouldResemble, []int{8002, 8001}) 459 So(query(`status:abandoned`), ShouldResemble, []int{}) 460 So(query(`status:merged`), ShouldResemble, []int{8003}) 461 }) 462 Convey("label", func() { 463 So(query(`label:Commit-Queue>0`), ShouldResemble, []int{8002, 8001}) 464 So(query(`label:Commit-Queue>1`), ShouldResemble, []int{8001}) 465 So(query(`label:Code-Review>-1`), ShouldResemble, []int{8003}) 466 }) 467 Convey("Typical CV query", func() { 468 So(query(`label:Commit-Queue>0 status:NEW project:"infra/infra"`), 469 ShouldResemble, []int{8001}) 470 So(query(`label:Commit-Queue>0 status:NEW projects:"infra"`), 471 ShouldResemble, []int{8002, 8001}) 472 So(query(`label:Commit-Queue>0 status:NEW projects:"infra"`+ 473 ` after:"2011-02-03 04:06:30.000000000" `+ 474 `before:"2011-02-03 04:08:30.000000000"`), ShouldResemble, []int{8002}) 475 So(query(`label:Commit-Queue>0 status:NEW `+ 476 `(project:"infra" OR project:"infra/luci/luci-go")`+ 477 ` after:"2011-02-03 04:06:30.000000000" `+ 478 `before:"2011-02-03 04:08:30.000000000"`), ShouldResemble, []int{8002}) 479 }) 480 }) 481 482 Convey("Bad queries", func() { 483 test := func(query string) error { 484 client, err := f.MakeClient(ctx, "infra", "chromium") 485 So(err, ShouldBeNil) 486 _, err = client.ListChanges(ctx, &gerritpb.ListChangesRequest{Query: query}) 487 So(grpcutil.Code(err), ShouldEqual, codes.InvalidArgument) 488 So(err, ShouldErrLike, `invalid query argument`) 489 return err 490 } 491 492 So(test(`"unmatched quote`), ShouldErrLike, `invalid query argument "\"unmatched quote"`) 493 So(test(`status:new "unmatched`), ShouldErrLike, `unrecognized token "\"unmatched`) 494 So(test(`project:"unmatched`), ShouldErrLike, `"project:\"unmatched": expected quoted string`) 495 So(test(`project:raw/not/supported`), ShouldErrLike, `expected quoted string`) 496 So(test(`project:"one" OR project:"two"`), ShouldErrLike, `"OR" must be inside ()`) 497 So(test(`project:"one" project:"two")`), ShouldErrLike, `"project:" must be inside ()`) 498 // This error can be better, but UX isn't essential for a fake. 499 So(test(`(project:"one" OR`), ShouldErrLike, `"" must be outside of ()`) 500 501 So(test(`status:rand-om`), ShouldErrLike, `unrecognized status "rand-om"`) 502 So(test(`status:0`), ShouldErrLike, `unrecognized status "0"`) 503 So(test(`label:0`), ShouldErrLike, `invalid label: 0`) 504 So(test(`label:Commit-Queue`), ShouldErrLike, `invalid label: Commit-Queue`) 505 506 // Note these are actually allowed in Gerrit. 507 So(test(`label:Commit-Queue<1`), ShouldErrLike, `invalid label: Commit-Queue<1`) 508 So(test(`before:2019-20-01`), ShouldErrLike, `failed to parse Gerrit timestamp "2019-20-01"`) 509 So(test(` after:2019-20-01`), ShouldErrLike, `failed to parse Gerrit timestamp "2019-20-01"`) 510 So(test(`before:"2019-20-01"`), ShouldErrLike, `failed to parse Gerrit timestamp "\"2019-20-01\""`) 511 }) 512 }) 513 } 514 515 func TestSetReview(t *testing.T) { 516 t.Parallel() 517 518 Convey("SetReview", t, func() { 519 ctx, tc := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC) 520 user := U("user-123") 521 accountID := user.AccountId 522 before := tc.Now().Add(-1 * time.Minute) 523 ciBefore := CI(10001, CQ(1, before, user), Updated(before)) 524 f := WithCIs( 525 "example", 526 ACLGrant(OpReview, codes.PermissionDenied, "chromium").Or(ACLGrant(OpAlterVotesOfOthers, codes.PermissionDenied, "chromium")), 527 ciBefore, 528 ) 529 tc.Add(2 * time.Minute) 530 531 mustWriterClient := func(host, luciProject string) gerrit.Client { 532 cl, err := f.MakeClient(ctx, host, luciProject) 533 So(err, ShouldBeNil) 534 return cl 535 } 536 537 latestCI := func() *gerritpb.ChangeInfo { 538 return f.GetChange("example", 10001).Info 539 } 540 Convey("ACLs enforced", func() { 541 client := mustWriterClient("example", "not-chromium") 542 res, err := client.SetReview(ctx, &gerritpb.SetReviewRequest{ 543 Number: 11111, 544 }) 545 So(res, ShouldBeNil) 546 So(grpcutil.Code(err), ShouldEqual, codes.NotFound) 547 548 res, err = client.SetReview(ctx, &gerritpb.SetReviewRequest{ 549 Number: 10001, 550 Message: "this is a message", 551 }) 552 So(res, ShouldBeNil) 553 So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied) 554 555 res, err = client.SetReview(ctx, &gerritpb.SetReviewRequest{ 556 Number: 10001, 557 Labels: map[string]int32{ 558 "Commit-Queue": 0, 559 }, 560 }) 561 So(res, ShouldBeNil) 562 So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied) 563 564 res, err = client.SetReview(ctx, &gerritpb.SetReviewRequest{ 565 Number: 10001, 566 Labels: map[string]int32{ 567 "Commit-Queue": 0, 568 }, 569 OnBehalfOf: accountID, 570 }) 571 So(res, ShouldBeNil) 572 So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied) 573 }) 574 575 Convey("Post message", func() { 576 client := mustWriterClient("example", "chromium") 577 res, err := client.SetReview(ctx, &gerritpb.SetReviewRequest{ 578 Number: 10001, 579 Message: "this is a message", 580 }) 581 So(err, ShouldBeNil) 582 So(res, ShouldResembleProto, &gerritpb.ReviewResult{}) 583 So(latestCI().GetUpdated().AsTime(), ShouldHappenAfter, ciBefore.GetUpdated().AsTime()) 584 So(latestCI().GetMessages(), ShouldResembleProto, []*gerritpb.ChangeMessageInfo{ 585 { 586 Id: "0", 587 Author: U("chromium"), 588 Date: timestamppb.New(tc.Now()), 589 Message: "this is a message", 590 }, 591 }) 592 }) 593 594 Convey("Set vote", func() { 595 client := mustWriterClient("example", "chromium") 596 res, err := client.SetReview(ctx, &gerritpb.SetReviewRequest{ 597 Number: 10001, 598 Labels: map[string]int32{ 599 "Commit-Queue": 2, 600 }, 601 }) 602 So(err, ShouldBeNil) 603 So(res, ShouldResembleProto, &gerritpb.ReviewResult{ 604 Labels: map[string]int32{ 605 "Commit-Queue": 2, 606 }, 607 }) 608 So(latestCI().GetUpdated().AsTime(), ShouldHappenAfter, ciBefore.GetUpdated().AsTime()) 609 So(latestCI().GetLabels()["Commit-Queue"].GetAll(), ShouldResembleProto, []*gerritpb.ApprovalInfo{ 610 { 611 User: user, 612 Value: 1, 613 Date: timestamppb.New(before), 614 }, 615 { 616 User: U("chromium"), 617 Value: 2, 618 Date: timestamppb.New(tc.Now()), 619 }, 620 }) 621 }) 622 623 Convey("Set vote on behalf of", func() { 624 client := mustWriterClient("example", "chromium") 625 Convey("existing voter", func() { 626 res, err := client.SetReview(ctx, &gerritpb.SetReviewRequest{ 627 Number: 10001, 628 Labels: map[string]int32{ 629 "Commit-Queue": 0, 630 }, 631 OnBehalfOf: 123, 632 }) 633 So(err, ShouldBeNil) 634 So(res, ShouldResembleProto, &gerritpb.ReviewResult{ 635 Labels: map[string]int32{ 636 "Commit-Queue": 0, 637 }, 638 }) 639 So(latestCI().GetUpdated().AsTime(), ShouldHappenAfter, ciBefore.GetUpdated().AsTime()) 640 So(NonZeroVotes(latestCI(), "Commit-Queue"), ShouldBeEmpty) 641 }) 642 643 Convey("new voter", func() { 644 res, err := client.SetReview(ctx, &gerritpb.SetReviewRequest{ 645 Number: 10001, 646 Labels: map[string]int32{ 647 "Commit-Queue": 1, 648 }, 649 OnBehalfOf: 789, 650 }) 651 So(err, ShouldBeNil) 652 So(res, ShouldResembleProto, &gerritpb.ReviewResult{ 653 Labels: map[string]int32{ 654 "Commit-Queue": 1, 655 }, 656 }) 657 So(latestCI().GetUpdated().AsTime(), ShouldHappenAfter, ciBefore.GetUpdated().AsTime()) 658 So(latestCI().GetLabels()["Commit-Queue"].GetAll(), ShouldResembleProto, []*gerritpb.ApprovalInfo{ 659 { 660 User: user, 661 Value: 1, 662 Date: timestamppb.New(before), 663 }, 664 { 665 User: U("user-789"), 666 Value: 1, 667 Date: timestamppb.New(tc.Now()), 668 }, 669 }) 670 }) 671 }) 672 }) 673 } 674 675 func TestSubmitRevision(t *testing.T) { 676 t.Parallel() 677 678 Convey("SubmitRevision", t, func() { 679 const gHost = "example.com" 680 ctx, tc := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC) 681 var ( 682 ciSingular = CI(10001, Updated(tc.Now()), PS(3), AllRevs()) 683 ciStackBase = CI(20001, Updated(tc.Now()), PS(1), AllRevs()) 684 ciStackMid = CI(20002, Updated(tc.Now()), PS(1), AllRevs()) 685 ciStackTop = CI(20003, Updated(tc.Now()), PS(1), AllRevs()) 686 ) 687 f := WithCIs( 688 "example.com", 689 ACLGrant(OpSubmit, codes.PermissionDenied, "chromium"), 690 ciSingular, ciStackBase, ciStackMid, ciStackTop, 691 ) 692 f.SetDependsOn(gHost, ciStackMid, ciStackBase) 693 f.SetDependsOn(gHost, ciStackTop, ciStackMid) 694 695 tc.Add(2 * time.Minute) 696 697 assertStatus := func(s gerritpb.ChangeStatus, cis ...*gerritpb.ChangeInfo) { 698 for _, ci := range cis { 699 latestCI := f.GetChange(gHost, int(ci.GetNumber())).Info 700 So(latestCI.GetStatus(), ShouldEqual, s) 701 } 702 } 703 704 mustWriterClient := func(host, luciProject string) gerrit.Client { 705 cl, err := f.MakeClient(ctx, host, luciProject) 706 So(err, ShouldBeNil) 707 return cl 708 } 709 710 Convey("ACLs enforced", func() { 711 client := mustWriterClient(gHost, "not-chromium") 712 _, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 713 Number: ciSingular.GetNumber(), 714 RevisionId: ciSingular.GetCurrentRevision(), 715 }) 716 So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied) 717 assertStatus(gerritpb.ChangeStatus_NEW, ciSingular) 718 }) 719 720 Convey("ACLs enforced with CL stack", func() { 721 f.MutateChange(gHost, int(ciStackBase.GetNumber()), func(c *Change) { 722 c.ACLs = ACLGrant(OpSubmit, codes.PermissionDenied, "pretty-much-denied-to-everyone") 723 }) 724 client := mustWriterClient(gHost, "chromium") 725 _, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 726 Number: ciStackTop.GetNumber(), 727 RevisionId: ciStackTop.GetCurrentRevision(), 728 }) 729 So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied) 730 assertStatus(gerritpb.ChangeStatus_NEW, ciStackBase, ciStackMid, ciStackTop) 731 }) 732 733 Convey("Non-existent revision", func() { 734 client := mustWriterClient(gHost, "chromium") 735 _, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 736 Number: ciSingular.GetNumber(), 737 RevisionId: "non-existent", 738 }) 739 So(grpcutil.Code(err), ShouldEqual, codes.NotFound) 740 assertStatus(gerritpb.ChangeStatus_NEW, ciSingular) 741 }) 742 743 Convey("Old revision", func() { 744 client := mustWriterClient(gHost, "chromium") 745 var oldRev string 746 for rev := range ciSingular.GetRevisions() { 747 if rev != ciSingular.GetCurrentRevision() { 748 oldRev = rev 749 break 750 } 751 } 752 So(oldRev, ShouldNotBeEmpty) 753 _, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 754 Number: ciSingular.GetNumber(), 755 RevisionId: oldRev, 756 }) 757 So(grpcutil.Code(err), ShouldEqual, codes.FailedPrecondition) 758 So(err, ShouldErrLike, "is not current") 759 }) 760 761 Convey("Works", func() { 762 client := mustWriterClient(gHost, "chromium") 763 res, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 764 Number: ciSingular.GetNumber(), 765 RevisionId: ciSingular.GetCurrentRevision(), 766 }) 767 So(err, ShouldBeNil) 768 So(res, ShouldResembleProto, &gerritpb.SubmitInfo{ 769 Status: gerritpb.ChangeStatus_MERGED, 770 }) 771 assertStatus(gerritpb.ChangeStatus_MERGED, ciSingular) 772 }) 773 774 Convey("Works with CL stack", func() { 775 client := mustWriterClient(gHost, "chromium") 776 res, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 777 Number: ciStackTop.GetNumber(), 778 RevisionId: ciStackTop.GetCurrentRevision(), 779 }) 780 So(err, ShouldBeNil) 781 So(res, ShouldResembleProto, &gerritpb.SubmitInfo{ 782 Status: gerritpb.ChangeStatus_MERGED, 783 }) 784 assertStatus(gerritpb.ChangeStatus_MERGED, ciStackBase, ciStackMid, ciStackTop) 785 }) 786 787 Convey("Already Merged", func() { 788 f.MutateChange(gHost, int(ciSingular.GetNumber()), func(c *Change) { 789 c.Info.Status = gerritpb.ChangeStatus_MERGED 790 }) 791 client := mustWriterClient(gHost, "chromium") 792 _, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 793 Number: ciSingular.GetNumber(), 794 RevisionId: ciSingular.GetCurrentRevision(), 795 }) 796 So(grpcutil.Code(err), ShouldEqual, codes.FailedPrecondition) 797 So(err, ShouldErrLike, "change is merged") 798 }) 799 800 Convey("already merged ones are skipped inside a CL Stack", func() { 801 verify := func() { 802 client := mustWriterClient(gHost, "chromium") 803 res, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 804 Number: ciStackTop.GetNumber(), 805 RevisionId: ciStackTop.GetCurrentRevision(), 806 }) 807 So(err, ShouldBeNil) 808 So(res, ShouldResembleProto, &gerritpb.SubmitInfo{ 809 Status: gerritpb.ChangeStatus_MERGED, 810 }) 811 } 812 Convey("base of stack", func() { 813 f.MutateChange(gHost, int(ciStackBase.GetNumber()), func(c *Change) { 814 c.Info.Status = gerritpb.ChangeStatus_MERGED 815 }) 816 verify() 817 assertStatus(gerritpb.ChangeStatus_MERGED, ciStackBase, ciStackMid, ciStackTop) 818 }) 819 Convey("mid-stack", func() { 820 // May happen if the mid-CL was at some point re-based on top of 821 // something other than ciStackBase and then submitted. 822 f.MutateChange(gHost, int(ciStackMid.GetNumber()), func(c *Change) { 823 c.Info.Status = gerritpb.ChangeStatus_MERGED 824 PS(int(c.Info.GetRevisions()[c.Info.GetCurrentRevision()].GetNumber() + 1))(c.Info) 825 }) 826 verify() 827 assertStatus(gerritpb.ChangeStatus_MERGED, ciStackMid, ciStackTop) 828 assertStatus(gerritpb.ChangeStatus_NEW, ciStackBase) 829 }) 830 }) 831 832 Convey("Abandoned", func() { 833 f.MutateChange(gHost, int(ciSingular.GetNumber()), func(c *Change) { 834 c.Info.Status = gerritpb.ChangeStatus_ABANDONED 835 }) 836 client := mustWriterClient(gHost, "chromium") 837 _, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 838 Number: ciSingular.GetNumber(), 839 RevisionId: ciSingular.GetCurrentRevision(), 840 }) 841 So(grpcutil.Code(err), ShouldEqual, codes.FailedPrecondition) 842 So(err, ShouldErrLike, "change is abandoned") 843 }) 844 Convey("Abandoned inside CL stack", func() { 845 f.MutateChange(gHost, int(ciStackMid.GetNumber()), func(c *Change) { 846 c.Info.Status = gerritpb.ChangeStatus_ABANDONED 847 }) 848 client := mustWriterClient(gHost, "chromium") 849 _, err := client.SubmitRevision(ctx, &gerritpb.SubmitRevisionRequest{ 850 Number: ciStackTop.GetNumber(), 851 RevisionId: ciStackTop.GetCurrentRevision(), 852 }) 853 So(grpcutil.Code(err), ShouldEqual, codes.FailedPrecondition) 854 So(err, ShouldErrLike, "change is abandoned") 855 }) 856 }) 857 }