go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/rpc/get_build_status_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  	"go.chromium.org/luci/auth/identity"
    22  	"go.chromium.org/luci/gae/impl/memory"
    23  	"go.chromium.org/luci/gae/service/datastore"
    24  	"go.chromium.org/luci/server/auth"
    25  	"go.chromium.org/luci/server/auth/authtest"
    26  
    27  	"go.chromium.org/luci/buildbucket/appengine/model"
    28  	"go.chromium.org/luci/buildbucket/appengine/rpc/testutil"
    29  	"go.chromium.org/luci/buildbucket/bbperms"
    30  	pb "go.chromium.org/luci/buildbucket/proto"
    31  
    32  	. "github.com/smartystreets/goconvey/convey"
    33  	. "go.chromium.org/luci/common/testing/assertions"
    34  )
    35  
    36  func TestValidateGetBuildStatusRequest(t *testing.T) {
    37  	t.Parallel()
    38  	Convey("ValidateGetBuildStatusRequest", t, func() {
    39  		Convey("nil", func() {
    40  			err := validateGetBuildStatusRequest(nil)
    41  			So(err, ShouldErrLike, "either id or (builder + build_number) is required")
    42  		})
    43  
    44  		Convey("empty", func() {
    45  			req := &pb.GetBuildStatusRequest{}
    46  			err := validateGetBuildStatusRequest(req)
    47  			So(err, ShouldErrLike, "either id or (builder + build_number) is required")
    48  		})
    49  
    50  		Convey("builder", func() {
    51  			req := &pb.GetBuildStatusRequest{
    52  				Builder: &pb.BuilderID{},
    53  			}
    54  			err := validateGetBuildStatusRequest(req)
    55  			So(err, ShouldErrLike, "either id or (builder + build_number) is required")
    56  		})
    57  
    58  		Convey("build number", func() {
    59  			req := &pb.GetBuildStatusRequest{
    60  				BuildNumber: 1,
    61  			}
    62  			err := validateGetBuildStatusRequest(req)
    63  			So(err, ShouldErrLike, "either id or (builder + build_number) is required")
    64  		})
    65  
    66  		Convey("mutual exclusion", func() {
    67  			Convey("builder", func() {
    68  				req := &pb.GetBuildStatusRequest{
    69  					Id:      1,
    70  					Builder: &pb.BuilderID{},
    71  				}
    72  				err := validateGetBuildStatusRequest(req)
    73  				So(err, ShouldErrLike, "id is mutually exclusive with (builder + build_number)")
    74  			})
    75  
    76  			Convey("build number", func() {
    77  				req := &pb.GetBuildStatusRequest{
    78  					Id:          1,
    79  					BuildNumber: 1,
    80  				}
    81  				err := validateGetBuildStatusRequest(req)
    82  				So(err, ShouldErrLike, "id is mutually exclusive with (builder + build_number)")
    83  			})
    84  		})
    85  
    86  		Convey("builder ID", func() {
    87  			Convey("project", func() {
    88  				req := &pb.GetBuildStatusRequest{
    89  					Builder:     &pb.BuilderID{},
    90  					BuildNumber: 1,
    91  				}
    92  				err := validateGetBuildStatusRequest(req)
    93  				So(err, ShouldErrLike, "project must match")
    94  			})
    95  
    96  			Convey("bucket", func() {
    97  				Convey("empty", func() {
    98  					req := &pb.GetBuildStatusRequest{
    99  						Builder: &pb.BuilderID{
   100  							Project: "project",
   101  						},
   102  						BuildNumber: 1,
   103  					}
   104  					err := validateGetBuildStatusRequest(req)
   105  					So(err, ShouldErrLike, "bucket is required")
   106  				})
   107  
   108  				Convey("v1", func() {
   109  					req := &pb.GetBuildStatusRequest{
   110  						Builder: &pb.BuilderID{
   111  							Project: "project",
   112  							Bucket:  "luci.project.bucket",
   113  							Builder: "builder",
   114  						},
   115  						BuildNumber: 1,
   116  					}
   117  					err := validateGetBuildStatusRequest(req)
   118  					So(err, ShouldErrLike, "invalid use of v1 bucket in v2 API")
   119  				})
   120  			})
   121  
   122  			Convey("builder", func() {
   123  				req := &pb.GetBuildStatusRequest{
   124  					Builder: &pb.BuilderID{
   125  						Project: "project",
   126  						Bucket:  "bucket",
   127  					},
   128  					BuildNumber: 1,
   129  				}
   130  				err := validateGetBuildStatusRequest(req)
   131  				So(err, ShouldErrLike, "builder is required")
   132  			})
   133  		})
   134  	})
   135  }
   136  
   137  func TestGetBuildStatus(t *testing.T) {
   138  	Convey("GetBuildStatus", t, func() {
   139  		srv := &Builds{}
   140  		ctx := memory.Use(context.Background())
   141  		datastore.GetTestable(ctx).AutoIndex(true)
   142  		datastore.GetTestable(ctx).Consistent(true)
   143  		testutil.PutBucket(ctx, "project", "bucket", nil)
   144  
   145  		Convey("no access", func() {
   146  			ctx = auth.WithState(ctx, &authtest.FakeState{
   147  				Identity: identity.Identity("user:unauthorized@example.com"),
   148  			})
   149  			bID := 123
   150  			bs := &model.BuildStatus{
   151  				Build:        datastore.MakeKey(ctx, "Build", bID),
   152  				BuildAddress: "project/bucket/builder/123",
   153  				Status:       pb.Status_SCHEDULED,
   154  			}
   155  			So(datastore.Put(ctx, bs), ShouldBeNil)
   156  			_, err := srv.GetBuildStatus(ctx, &pb.GetBuildStatusRequest{
   157  				Id: int64(bID),
   158  			})
   159  			So(err, ShouldErrLike, `requested resource not found or "user:unauthorized@example.com" does not have permission to view it`)
   160  		})
   161  
   162  		Convey("get build status", func() {
   163  			const userID = identity.Identity("user:user@example.com")
   164  			ctx = auth.WithState(ctx, &authtest.FakeState{
   165  				Identity: userID,
   166  				FakeDB: authtest.NewFakeDB(
   167  					authtest.MockPermission(userID, "project:bucket", bbperms.BuildsGet),
   168  				),
   169  			})
   170  
   171  			Convey("builder not found for BuildStatus entity", func() {
   172  				bID := 123
   173  				bs := &model.BuildStatus{
   174  					Build:  datastore.MakeKey(ctx, "Build", bID),
   175  					Status: pb.Status_SCHEDULED,
   176  				}
   177  				So(datastore.Put(ctx, bs), ShouldBeNil)
   178  				_, err := srv.GetBuildStatus(ctx, &pb.GetBuildStatusRequest{
   179  					Id: int64(bID),
   180  				})
   181  				So(err, ShouldErrLike, `failed to parse build_address of build 123`)
   182  			})
   183  
   184  			Convey("id", func() {
   185  				bID := 123
   186  				bs := &model.BuildStatus{
   187  					Build:        datastore.MakeKey(ctx, "Build", bID),
   188  					BuildAddress: "project/bucket/builder/123",
   189  					Status:       pb.Status_SCHEDULED,
   190  				}
   191  				So(datastore.Put(ctx, bs), ShouldBeNil)
   192  				b, err := srv.GetBuildStatus(ctx, &pb.GetBuildStatusRequest{
   193  					Id: int64(bID),
   194  				})
   195  				So(err, ShouldBeNil)
   196  				So(b, ShouldResembleProto, &pb.Build{
   197  					Id:     int64(bID),
   198  					Status: pb.Status_SCHEDULED,
   199  				})
   200  			})
   201  
   202  			Convey("id by getBuild", func() {
   203  				bID := 123
   204  				bld := &model.Build{
   205  					ID: 123,
   206  					Proto: &pb.Build{
   207  						Id: 123,
   208  						Builder: &pb.BuilderID{
   209  							Project: "project",
   210  							Bucket:  "bucket",
   211  							Builder: "builder",
   212  						},
   213  						Status: pb.Status_STARTED,
   214  					},
   215  				}
   216  				So(datastore.Put(ctx, bld), ShouldBeNil)
   217  				b, err := srv.GetBuildStatus(ctx, &pb.GetBuildStatusRequest{
   218  					Id: int64(bID),
   219  				})
   220  				So(err, ShouldBeNil)
   221  				So(b, ShouldResembleProto, &pb.Build{
   222  					Id:     int64(bID),
   223  					Status: pb.Status_STARTED,
   224  				})
   225  			})
   226  
   227  			Convey("builder + number", func() {
   228  				bs := &model.BuildStatus{
   229  					Build:        datastore.MakeKey(ctx, "Build", 333),
   230  					BuildAddress: "project/bucket/builder/3",
   231  					Status:       pb.Status_SCHEDULED,
   232  				}
   233  				So(datastore.Put(ctx, bs), ShouldBeNil)
   234  				b, err := srv.GetBuildStatus(ctx, &pb.GetBuildStatusRequest{
   235  					Builder: &pb.BuilderID{
   236  						Project: "project",
   237  						Bucket:  "bucket",
   238  						Builder: "builder",
   239  					},
   240  					BuildNumber: 3,
   241  				})
   242  				So(err, ShouldBeNil)
   243  				So(b, ShouldResembleProto, &pb.Build{
   244  					Builder: &pb.BuilderID{
   245  						Project: "project",
   246  						Bucket:  "bucket",
   247  						Builder: "builder",
   248  					},
   249  					Number: 3,
   250  					Status: pb.Status_SCHEDULED,
   251  				})
   252  			})
   253  
   254  			Convey("builder + number by getBuild", func() {
   255  				So(datastore.Put(ctx, &model.Build{
   256  					ID: 333,
   257  					Proto: &pb.Build{
   258  						Id: 333,
   259  						Builder: &pb.BuilderID{
   260  							Project: "project",
   261  							Bucket:  "bucket",
   262  							Builder: "builder",
   263  						},
   264  						Number: 3,
   265  						Status: pb.Status_STARTED,
   266  					},
   267  					BucketID: "project/bucket",
   268  					Tags:     []string{"build_address:luci.project.bucket/builder/1"},
   269  				}), ShouldBeNil)
   270  				So(datastore.Put(ctx, &model.TagIndex{
   271  					ID: ":2:build_address:luci.project.bucket/builder/3",
   272  					Entries: []model.TagIndexEntry{
   273  						{
   274  							BuildID:  333,
   275  							BucketID: "project/bucket",
   276  						},
   277  					},
   278  				}), ShouldBeNil)
   279  				b, err := srv.GetBuildStatus(ctx, &pb.GetBuildStatusRequest{
   280  					Builder: &pb.BuilderID{
   281  						Project: "project",
   282  						Bucket:  "bucket",
   283  						Builder: "builder",
   284  					},
   285  					BuildNumber: 3,
   286  				})
   287  				So(err, ShouldBeNil)
   288  				So(b, ShouldResembleProto, &pb.Build{
   289  					Builder: &pb.BuilderID{
   290  						Project: "project",
   291  						Bucket:  "bucket",
   292  						Builder: "builder",
   293  					},
   294  					Number: 3,
   295  					Status: pb.Status_STARTED,
   296  				})
   297  			})
   298  		})
   299  	})
   300  }