go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/rpc/projects_test.go (about)

     1  // Copyright 2022 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/grpc/codes"
    22  	grpcStatus "google.golang.org/grpc/status"
    23  	"google.golang.org/protobuf/proto"
    24  
    25  	"go.chromium.org/luci/gae/impl/memory"
    26  	"go.chromium.org/luci/server/auth"
    27  	"go.chromium.org/luci/server/auth/authtest"
    28  	"go.chromium.org/luci/server/secrets"
    29  	"go.chromium.org/luci/server/secrets/testsecrets"
    30  
    31  	"go.chromium.org/luci/analysis/internal/analysis/metrics"
    32  	"go.chromium.org/luci/analysis/internal/config"
    33  	"go.chromium.org/luci/analysis/internal/perms"
    34  	configpb "go.chromium.org/luci/analysis/proto/config"
    35  	pb "go.chromium.org/luci/analysis/proto/v1"
    36  
    37  	. "github.com/smartystreets/goconvey/convey"
    38  	. "go.chromium.org/luci/common/testing/assertions"
    39  )
    40  
    41  func TestProjects(t *testing.T) {
    42  	Convey("Given a projects server", t, func() {
    43  		ctx := context.Background()
    44  
    45  		// For user identification.
    46  		ctx = authtest.MockAuthConfig(ctx)
    47  		authState := &authtest.FakeState{
    48  			Identity:       "user:someone@example.com",
    49  			IdentityGroups: []string{"luci-analysis-access"},
    50  		}
    51  		ctx = auth.WithState(ctx, authState)
    52  		ctx = secrets.Use(ctx, &testsecrets.Store{})
    53  
    54  		// Provides datastore implementation needed for project config.
    55  		ctx = memory.Use(ctx)
    56  		server := NewProjectsServer()
    57  
    58  		Convey("Unauthorised requests are rejected", func() {
    59  			ctx = auth.WithState(ctx, &authtest.FakeState{
    60  				Identity: "user:someone@example.com",
    61  				// Not a member of luci-analysis-access.
    62  				IdentityGroups: []string{"other-group"},
    63  			})
    64  
    65  			// Make some request (the request should not matter, as
    66  			// a common decorator is used for all requests.)
    67  			request := &pb.ListProjectsRequest{}
    68  
    69  			rule, err := server.List(ctx, request)
    70  			st, _ := grpcStatus.FromError(err)
    71  			So(st.Code(), ShouldEqual, codes.PermissionDenied)
    72  			So(st.Message(), ShouldEqual, "not a member of luci-analysis-access")
    73  			So(rule, ShouldBeNil)
    74  		})
    75  		Convey("GetConfig", func() {
    76  			authState.IdentityPermissions = []authtest.RealmPermission{
    77  				{
    78  					Realm:      "testproject:@project",
    79  					Permission: perms.PermGetConfig,
    80  				},
    81  			}
    82  
    83  			// Setup.
    84  			configs := make(map[string]*configpb.ProjectConfig)
    85  			projectCfg := config.CreateConfigWithBothBuganizerAndMonorail(configpb.BugSystem_BUGANIZER)
    86  
    87  			configs["testproject"] = projectCfg
    88  			So(config.SetTestProjectConfig(ctx, configs), ShouldBeNil)
    89  
    90  			request := &pb.GetProjectConfigRequest{
    91  				Name: "projects/testproject/config",
    92  			}
    93  
    94  			Convey("No permission to get project config", func() {
    95  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermGetConfig)
    96  
    97  				response, err := server.GetConfig(ctx, request)
    98  				So(err, ShouldBeRPCPermissionDenied, "caller does not have permission analysis.config.get")
    99  				So(response, ShouldBeNil)
   100  			})
   101  			Convey("Valid request", func() {
   102  				expectedResponse := &pb.ProjectConfig{
   103  					Name: "projects/testproject/config",
   104  					BugManagement: &pb.BugManagement{
   105  						Policies: []*pb.BugManagementPolicy{
   106  							{
   107  								Id:                "exoneration",
   108  								Owners:            []string{"username@google.com"},
   109  								HumanReadableName: "test variant(s) are being exonerated in presubmit",
   110  								Priority:          pb.BuganizerPriority_P2,
   111  								Metrics: []*pb.BugManagementPolicy_Metric{
   112  									{
   113  										MetricId: metrics.CriticalFailuresExonerated.ID.String(),
   114  										ActivationThreshold: &pb.MetricThreshold{
   115  											OneDay: proto.Int64(50),
   116  										},
   117  										DeactivationThreshold: &pb.MetricThreshold{
   118  											ThreeDay: proto.Int64(20),
   119  										},
   120  									},
   121  								},
   122  								Explanation: &pb.BugManagementPolicy_Explanation{
   123  									ProblemHtml: "Test variant(s) in the cluster are being exonerated because they are too flaky or failing.",
   124  									ActionHtml:  "<ul><li>View recent failures and fix them</li><li>Demote the test(s) from CQ</li></ul>",
   125  								},
   126  							},
   127  						},
   128  						Monorail: &pb.MonorailProject{
   129  							Project:       "chromium",
   130  							DisplayPrefix: "crbug.com",
   131  						},
   132  					},
   133  				}
   134  
   135  				Convey("baseline", func() {
   136  					// Run
   137  					response, err := server.GetConfig(ctx, request)
   138  
   139  					// Verify
   140  					So(err, ShouldBeNil)
   141  					So(response, ShouldResembleProto, expectedResponse)
   142  				})
   143  				Convey("without monorail config", func() {
   144  					projectCfg.BugManagement.Monorail = nil
   145  					So(config.SetTestProjectConfig(ctx, configs), ShouldBeNil)
   146  
   147  					// Run
   148  					response, err := server.GetConfig(ctx, request)
   149  
   150  					// Verify
   151  					So(err, ShouldBeNil)
   152  					expectedResponse.BugManagement.Monorail = nil
   153  					So(response, ShouldResembleProto, expectedResponse)
   154  				})
   155  				Convey("policy without activation threshold", func() {
   156  					projectCfg.BugManagement.Policies[0].Metrics[0].ActivationThreshold = nil
   157  					So(config.SetTestProjectConfig(ctx, configs), ShouldBeNil)
   158  
   159  					// Run
   160  					response, err := server.GetConfig(ctx, request)
   161  
   162  					// Verify
   163  					So(err, ShouldBeNil)
   164  					expectedResponse.BugManagement.Policies[0].Metrics[0].ActivationThreshold = nil
   165  					So(response, ShouldResembleProto, expectedResponse)
   166  				})
   167  			})
   168  			Convey("With project not configured", func() {
   169  				err := config.SetTestProjectConfig(ctx, map[string]*configpb.ProjectConfig{})
   170  				So(err, ShouldBeNil)
   171  
   172  				// Run
   173  				response, err := server.GetConfig(ctx, request)
   174  
   175  				// Verify
   176  				So(err, ShouldBeNil)
   177  				So(response, ShouldResembleProto, &pb.ProjectConfig{
   178  					Name:          "projects/testproject/config",
   179  					BugManagement: &pb.BugManagement{},
   180  				})
   181  			})
   182  			Convey("Invalid request", func() {
   183  				request.Name = "blah"
   184  
   185  				// Run
   186  				response, err := server.GetConfig(ctx, request)
   187  
   188  				// Verify
   189  				So(err, ShouldBeRPCInvalidArgument, "name: invalid project config name, expected format: projects/{project}/config")
   190  				So(response, ShouldBeNil)
   191  			})
   192  		})
   193  		Convey("List", func() {
   194  			authState.IdentityPermissions = []authtest.RealmPermission{
   195  				{
   196  					Realm:      "chromium:@project",
   197  					Permission: perms.PermGetConfig,
   198  				},
   199  				{
   200  					Realm:      "chrome:@project",
   201  					Permission: perms.PermGetConfig,
   202  				},
   203  				{
   204  					Realm:      "chromeos:@project",
   205  					Permission: perms.PermGetConfig,
   206  				},
   207  			}
   208  
   209  			request := &pb.ListProjectsRequest{}
   210  
   211  			Convey("No permission to view any project", func() {
   212  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermGetConfig)
   213  
   214  				// Run
   215  				projectsResponse, err := server.List(ctx, request)
   216  
   217  				// Verify
   218  				So(err, ShouldBeNil)
   219  				expected := &pb.ListProjectsResponse{Projects: []*pb.Project{}}
   220  				So(projectsResponse, ShouldResembleProto, expected)
   221  			})
   222  			Convey("Valid request", func() {
   223  				// Run
   224  				projectsResponse, err := server.List(ctx, request)
   225  
   226  				// Verify
   227  				So(err, ShouldBeNil)
   228  				expected := &pb.ListProjectsResponse{Projects: []*pb.Project{
   229  					{
   230  						Name:        "projects/chrome",
   231  						DisplayName: "Chrome",
   232  						Project:     "chrome",
   233  					},
   234  					{
   235  						Name:        "projects/chromeos",
   236  						DisplayName: "Chromeos",
   237  						Project:     "chromeos",
   238  					},
   239  					{
   240  						Name:        "projects/chromium",
   241  						DisplayName: "Chromium",
   242  						Project:     "chromium",
   243  					},
   244  				}}
   245  				So(projectsResponse, ShouldResembleProto, expected)
   246  			})
   247  		})
   248  	})
   249  }