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 }