go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/rpc/rules_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  	"fmt"
    19  	"sort"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"google.golang.org/protobuf/types/known/fieldmaskpb"
    25  	"google.golang.org/protobuf/types/known/timestamppb"
    26  
    27  	"go.chromium.org/luci/gae/impl/memory"
    28  	"go.chromium.org/luci/server/auth"
    29  	"go.chromium.org/luci/server/auth/authtest"
    30  	"go.chromium.org/luci/server/secrets"
    31  	"go.chromium.org/luci/server/secrets/testsecrets"
    32  	"go.chromium.org/luci/server/span"
    33  
    34  	"go.chromium.org/luci/analysis/internal/bugs"
    35  	bugspb "go.chromium.org/luci/analysis/internal/bugs/proto"
    36  	"go.chromium.org/luci/analysis/internal/clustering"
    37  	"go.chromium.org/luci/analysis/internal/clustering/algorithms/testname"
    38  	"go.chromium.org/luci/analysis/internal/clustering/rules"
    39  	"go.chromium.org/luci/analysis/internal/config"
    40  	"go.chromium.org/luci/analysis/internal/perms"
    41  	"go.chromium.org/luci/analysis/internal/testutil"
    42  	configpb "go.chromium.org/luci/analysis/proto/config"
    43  	pb "go.chromium.org/luci/analysis/proto/v1"
    44  
    45  	. "github.com/smartystreets/goconvey/convey"
    46  	. "go.chromium.org/luci/common/testing/assertions"
    47  )
    48  
    49  func TestRules(t *testing.T) {
    50  	Convey("With Server", t, func() {
    51  		ctx := testutil.IntegrationTestContext(t)
    52  
    53  		// For user identification.
    54  		ctx = authtest.MockAuthConfig(ctx)
    55  		authState := &authtest.FakeState{
    56  			Identity:       "user:someone@example.com",
    57  			IdentityGroups: []string{luciAnalysisAccessGroup, auditUsersAccessGroup},
    58  		}
    59  		ctx = auth.WithState(ctx, authState)
    60  		ctx = secrets.Use(ctx, &testsecrets.Store{})
    61  
    62  		// Provides datastore implementation needed for project config.
    63  		ctx = memory.Use(ctx)
    64  
    65  		srv := NewRulesSever()
    66  
    67  		ruleManagedBuilder := rules.NewRule(0).
    68  			WithProject(testProject).
    69  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/111"})
    70  		ruleManaged := ruleManagedBuilder.Build()
    71  		ruleTwoProject := rules.NewRule(1).
    72  			WithProject(testProject).
    73  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/222"}).
    74  			WithBugManaged(false).
    75  			Build()
    76  		ruleTwoProjectOther := rules.NewRule(2).
    77  			WithProject("otherproject").
    78  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/222"}).
    79  			Build()
    80  		ruleUnmanagedOther := rules.NewRule(3).
    81  			WithProject("otherproject").
    82  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/444"}).
    83  			WithBugManaged(false).
    84  			Build()
    85  		ruleManagedOther := rules.NewRule(4).
    86  			WithProject("otherproject").
    87  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/555"}).
    88  			WithBugManaged(true).
    89  			Build()
    90  		ruleBuganizer := rules.NewRule(5).
    91  			WithProject(testProject).
    92  			WithBug(bugs.BugID{System: "buganizer", ID: "666"}).
    93  			Build()
    94  
    95  		err := rules.SetForTesting(ctx, []*rules.Entry{
    96  			ruleManaged,
    97  			ruleTwoProject,
    98  			ruleTwoProjectOther,
    99  			ruleUnmanagedOther,
   100  			ruleManagedOther,
   101  			ruleBuganizer,
   102  		})
   103  		So(err, ShouldBeNil)
   104  
   105  		cfg := &configpb.ProjectConfig{
   106  			BugManagement: &configpb.BugManagement{
   107  				Monorail: &configpb.MonorailProject{
   108  					Project:          "monorailproject",
   109  					DisplayPrefix:    "mybug.com",
   110  					MonorailHostname: "monorailhost.com",
   111  				},
   112  			},
   113  		}
   114  		err = config.SetTestProjectConfig(ctx, map[string]*configpb.ProjectConfig{
   115  			"testproject": cfg,
   116  		})
   117  		So(err, ShouldBeNil)
   118  
   119  		Convey("Unauthorised requests are rejected", func() {
   120  			// Ensure no access to luci-analysis-access.
   121  			ctx = auth.WithState(ctx, &authtest.FakeState{
   122  				Identity: "user:someone@example.com",
   123  				// Not a member of luci-analysis-access.
   124  				IdentityGroups: []string{"other-group"},
   125  			})
   126  
   127  			// Make some request (the request should not matter, as
   128  			// a common decorator is used for all requests.)
   129  			request := &pb.GetRuleRequest{
   130  				Name: fmt.Sprintf("projects/%s/rules/%s", ruleManaged.Project, ruleManaged.RuleID),
   131  			}
   132  
   133  			rule, err := srv.Get(ctx, request)
   134  			So(err, ShouldBeRPCPermissionDenied, "not a member of luci-analysis-access")
   135  			So(rule, ShouldBeNil)
   136  		})
   137  		Convey("Get", func() {
   138  			authState.IdentityPermissions = []authtest.RealmPermission{
   139  				{
   140  					Realm:      "testproject:@project",
   141  					Permission: perms.PermGetRule,
   142  				},
   143  				{
   144  					Realm:      "testproject:@project",
   145  					Permission: perms.PermGetRuleDefinition,
   146  				},
   147  			}
   148  
   149  			Convey("No get rule permission", func() {
   150  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermGetRule)
   151  
   152  				request := &pb.GetRuleRequest{
   153  					Name: fmt.Sprintf("projects/%s/rules/%s", ruleManaged.Project, ruleManaged.RuleID),
   154  				}
   155  
   156  				rule, err := srv.Get(ctx, request)
   157  				So(err, ShouldBeRPCPermissionDenied, "caller does not have permission analysis.rules.get")
   158  				So(rule, ShouldBeNil)
   159  			})
   160  			Convey("Rule exists", func() {
   161  				mask := ruleMask{
   162  					IncludeDefinition: true,
   163  					IncludeAuditUsers: true,
   164  				}
   165  				Convey("Read rule with Monorail bug", func() {
   166  					Convey("Baseline", func() {
   167  						request := &pb.GetRuleRequest{
   168  							Name: fmt.Sprintf("projects/%s/rules/%s", ruleManaged.Project, ruleManaged.RuleID),
   169  						}
   170  
   171  						rule, err := srv.Get(ctx, request)
   172  						So(err, ShouldBeNil)
   173  						So(rule, ShouldResembleProto, createRulePB(ruleManaged, cfg, mask))
   174  					})
   175  					Convey("Without get rule definition permission", func() {
   176  						authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermGetRuleDefinition)
   177  
   178  						request := &pb.GetRuleRequest{
   179  							Name: fmt.Sprintf("projects/%s/rules/%s", ruleManaged.Project, ruleManaged.RuleID),
   180  						}
   181  
   182  						rule, err := srv.Get(ctx, request)
   183  						So(err, ShouldBeNil)
   184  						mask.IncludeDefinition = false
   185  						So(rule, ShouldResembleProto, createRulePB(ruleManaged, cfg, mask))
   186  					})
   187  					Convey("Without get rule audit users permission", func() {
   188  						authState.IdentityGroups = removeGroup(authState.IdentityGroups, auditUsersAccessGroup)
   189  
   190  						request := &pb.GetRuleRequest{
   191  							Name: fmt.Sprintf("projects/%s/rules/%s", ruleManaged.Project, ruleManaged.RuleID),
   192  						}
   193  
   194  						rule, err := srv.Get(ctx, request)
   195  						So(err, ShouldBeNil)
   196  						mask.IncludeAuditUsers = false
   197  						So(rule, ShouldResembleProto, createRulePB(ruleManaged, cfg, mask))
   198  					})
   199  				})
   200  				Convey("Read rule with Buganizer bug", func() {
   201  					request := &pb.GetRuleRequest{
   202  						Name: fmt.Sprintf("projects/%s/rules/%s", ruleBuganizer.Project, ruleBuganizer.RuleID),
   203  					}
   204  
   205  					rule, err := srv.Get(ctx, request)
   206  					So(err, ShouldBeNil)
   207  					So(rule, ShouldResembleProto, createRulePB(ruleBuganizer, cfg, mask))
   208  				})
   209  			})
   210  			Convey("Rule does not exist", func() {
   211  				ruleID := strings.Repeat("00", 16)
   212  				request := &pb.GetRuleRequest{
   213  					Name: fmt.Sprintf("projects/%s/rules/%s", ruleManaged.Project, ruleID),
   214  				}
   215  
   216  				rule, err := srv.Get(ctx, request)
   217  				So(rule, ShouldBeNil)
   218  				So(err, ShouldBeRPCNotFound)
   219  			})
   220  		})
   221  		Convey("List", func() {
   222  			authState.IdentityPermissions = []authtest.RealmPermission{
   223  				{
   224  					Realm:      "testproject:@project",
   225  					Permission: perms.PermListRules,
   226  				},
   227  				{
   228  					Realm:      "testproject:@project",
   229  					Permission: perms.PermGetRuleDefinition,
   230  				},
   231  			}
   232  
   233  			request := &pb.ListRulesRequest{
   234  				Parent: fmt.Sprintf("projects/%s", testProject),
   235  			}
   236  			Convey("No list rules permission", func() {
   237  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermListRules)
   238  
   239  				response, err := srv.List(ctx, request)
   240  				So(err, ShouldBeRPCPermissionDenied, "caller does not have permission analysis.rules.list")
   241  				So(response, ShouldBeNil)
   242  			})
   243  			Convey("Non-Empty", func() {
   244  				test := func(mask ruleMask, cfg *configpb.ProjectConfig) {
   245  					rs := []*rules.Entry{
   246  						ruleManaged,
   247  						ruleBuganizer,
   248  						rules.NewRule(2).WithProject(testProject).Build(),
   249  						rules.NewRule(3).WithProject(testProject).Build(),
   250  						rules.NewRule(4).WithProject(testProject).Build(),
   251  						// In other project.
   252  						ruleManagedOther,
   253  					}
   254  					err := rules.SetForTesting(ctx, rs)
   255  					So(err, ShouldBeNil)
   256  
   257  					response, err := srv.List(ctx, request)
   258  					So(err, ShouldBeNil)
   259  
   260  					expected := &pb.ListRulesResponse{
   261  						Rules: []*pb.Rule{
   262  							createRulePB(rs[0], cfg, mask),
   263  							createRulePB(rs[1], cfg, mask),
   264  							createRulePB(rs[2], cfg, mask),
   265  							createRulePB(rs[3], cfg, mask),
   266  							createRulePB(rs[4], cfg, mask),
   267  						},
   268  					}
   269  					sort.Slice(expected.Rules, func(i, j int) bool {
   270  						return expected.Rules[i].RuleId < expected.Rules[j].RuleId
   271  					})
   272  					sort.Slice(response.Rules, func(i, j int) bool {
   273  						return response.Rules[i].RuleId < response.Rules[j].RuleId
   274  					})
   275  					So(response, ShouldResembleProto, expected)
   276  				}
   277  				mask := ruleMask{
   278  					IncludeDefinition: true,
   279  					IncludeAuditUsers: true,
   280  				}
   281  				Convey("Baseline", func() {
   282  					test(mask, cfg)
   283  				})
   284  				Convey("Without get rule definition permission", func() {
   285  					authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermGetRuleDefinition)
   286  
   287  					mask.IncludeDefinition = false
   288  					test(mask, cfg)
   289  				})
   290  				Convey("Without get rule audit users permission", func() {
   291  					authState.IdentityGroups = removeGroup(authState.IdentityGroups, auditUsersAccessGroup)
   292  
   293  					mask.IncludeAuditUsers = false
   294  					test(mask, cfg)
   295  				})
   296  				Convey("With project not configured", func() {
   297  					err = config.SetTestProjectConfig(ctx, map[string]*configpb.ProjectConfig{})
   298  
   299  					test(mask, config.NewEmptyProject())
   300  				})
   301  			})
   302  			Convey("Empty", func() {
   303  				err := rules.SetForTesting(ctx, nil)
   304  				So(err, ShouldBeNil)
   305  
   306  				response, err := srv.List(ctx, request)
   307  				So(err, ShouldBeNil)
   308  
   309  				expected := &pb.ListRulesResponse{}
   310  				So(response, ShouldResembleProto, expected)
   311  			})
   312  		})
   313  		Convey("Update", func() {
   314  			authState.IdentityPermissions = []authtest.RealmPermission{
   315  				{
   316  					Realm:      "testproject:@project",
   317  					Permission: perms.PermUpdateRule,
   318  				},
   319  				{
   320  					Realm:      "testproject:@project",
   321  					Permission: perms.PermGetRuleDefinition,
   322  				},
   323  			}
   324  			request := &pb.UpdateRuleRequest{
   325  				Rule: &pb.Rule{
   326  					Name:           fmt.Sprintf("projects/%s/rules/%s", ruleManaged.Project, ruleManaged.RuleID),
   327  					RuleDefinition: `test = "updated"`,
   328  					Bug: &pb.AssociatedBug{
   329  						System: "monorail",
   330  						Id:     "monorailproject/2",
   331  					},
   332  					IsManagingBug:         false,
   333  					IsManagingBugPriority: false,
   334  					IsActive:              false,
   335  				},
   336  				UpdateMask: &fieldmaskpb.FieldMask{
   337  					// On the client side, we use JSON equivalents, i.e. ruleDefinition,
   338  					// bug, isActive, isManagingBug. The pRPC layer handles the
   339  					// translation before the request hits our service.
   340  					Paths: []string{"rule_definition", "bug", "is_active", "is_managing_bug", "is_managing_bug_priority"},
   341  				},
   342  				Etag: ruleETag(ruleManaged, ruleMask{IncludeDefinition: true, IncludeAuditUsers: false}),
   343  			}
   344  
   345  			Convey("No update rules permission", func() {
   346  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermUpdateRule)
   347  
   348  				rule, err := srv.Update(ctx, request)
   349  				So(err, ShouldBeRPCPermissionDenied, "caller does not have permission analysis.rules.update")
   350  				So(rule, ShouldBeNil)
   351  			})
   352  			Convey("No rule definition permission", func() {
   353  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermGetRuleDefinition)
   354  
   355  				rule, err := srv.Update(ctx, request)
   356  				So(err, ShouldBeRPCPermissionDenied, "caller does not have permission analysis.rules.getDefinition")
   357  				So(rule, ShouldBeNil)
   358  			})
   359  			Convey("Success", func() {
   360  				Convey("Predicate updated", func() {
   361  					// The requested updates should be applied.
   362  					expectedRule := ruleManagedBuilder.Build().Clone()
   363  					expectedRule.RuleDefinition = `test = "updated"`
   364  					expectedRule.BugID = bugs.BugID{System: "monorail", ID: "monorailproject/2"}
   365  					expectedRule.IsActive = false
   366  					expectedRule.IsManagingBug = false
   367  					expectedRule.IsManagingBugPriority = false
   368  
   369  					// The notification flags should be reset as the bug was changed.
   370  					expectedRule.BugManagementState.RuleAssociationNotified = false
   371  					for _, policyState := range expectedRule.BugManagementState.PolicyState {
   372  						policyState.ActivationNotified = false
   373  					}
   374  
   375  					Convey("baseline", func() {
   376  						// Act
   377  						rule, err := srv.Update(ctx, request)
   378  
   379  						// Verify
   380  						So(err, ShouldBeNil)
   381  
   382  						storedRule, err := rules.Read(span.Single(ctx), testProject, ruleManaged.RuleID)
   383  						So(err, ShouldBeNil)
   384  						So(storedRule.LastUpdateTime, ShouldNotEqual, ruleManaged.LastUpdateTime)
   385  
   386  						// Accept the new last update time (this value is set non-deterministically).
   387  						expectedRule.LastUpdateTime = storedRule.LastUpdateTime
   388  						expectedRule.LastAuditableUpdateTime = storedRule.LastUpdateTime
   389  						expectedRule.IsManagingBugPriorityLastUpdateTime = storedRule.LastUpdateTime
   390  						expectedRule.PredicateLastUpdateTime = storedRule.LastUpdateTime
   391  						expectedRule.LastAuditableUpdateUser = "someone@example.com"
   392  
   393  						// Verify the rule was updated as expected.
   394  						So(storedRule, ShouldResembleProto, expectedRule)
   395  
   396  						// Verify the returned rule matches what was expected.
   397  						mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
   398  						So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   399  					})
   400  					Convey("No audit users permission", func() {
   401  						authState.IdentityGroups = removeGroup(authState.IdentityGroups, auditUsersAccessGroup)
   402  
   403  						// Act
   404  						rule, err := srv.Update(ctx, request)
   405  
   406  						// Verify
   407  						So(err, ShouldBeNil)
   408  
   409  						storedRule, err := rules.Read(span.Single(ctx), testProject, ruleManaged.RuleID)
   410  						So(err, ShouldBeNil)
   411  						So(storedRule.LastUpdateTime, ShouldNotEqual, ruleManaged.LastUpdateTime)
   412  
   413  						// Accept the new last update time (this value is set non-deterministically).
   414  						expectedRule.LastUpdateTime = storedRule.LastUpdateTime
   415  						expectedRule.LastAuditableUpdateTime = storedRule.LastUpdateTime
   416  						expectedRule.PredicateLastUpdateTime = storedRule.LastUpdateTime
   417  						expectedRule.IsManagingBugPriorityLastUpdateTime = storedRule.LastUpdateTime
   418  						expectedRule.LastAuditableUpdateUser = "someone@example.com"
   419  
   420  						So(storedRule, ShouldResembleProto, expectedRule)
   421  
   422  						// Verify audit users are omitted from the result.
   423  						mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: false}
   424  						So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   425  					})
   426  				})
   427  				Convey("Predicate not updated", func() {
   428  					request.UpdateMask.Paths = []string{"bug"}
   429  					request.Rule.Bug = &pb.AssociatedBug{
   430  						System: "buganizer",
   431  						Id:     "99999999",
   432  					}
   433  
   434  					rule, err := srv.Update(ctx, request)
   435  					So(err, ShouldBeNil)
   436  
   437  					storedRule, err := rules.Read(span.Single(ctx), testProject, ruleManaged.RuleID)
   438  					So(err, ShouldBeNil)
   439  
   440  					// Check the rule was updated, but that predicate last
   441  					// updated time was NOT updated.
   442  					So(storedRule.LastUpdateTime, ShouldNotEqual, ruleManaged.LastUpdateTime)
   443  
   444  					// Verify the rule was correctly updated in the database:
   445  					// - The requested updates should be applied.
   446  					expectedRule := ruleManagedBuilder.Build()
   447  					expectedRule.BugID = bugs.BugID{System: "buganizer", ID: "99999999"}
   448  
   449  					// - The notification flags should be reset as the bug was changed.
   450  					expectedRule.BugManagementState.RuleAssociationNotified = false
   451  					for _, policyState := range expectedRule.BugManagementState.PolicyState {
   452  						policyState.ActivationNotified = false
   453  					}
   454  
   455  					// - Accept the new last update time (this value is set non-deterministically).
   456  					expectedRule.LastUpdateTime = storedRule.LastUpdateTime
   457  					expectedRule.LastAuditableUpdateTime = storedRule.LastUpdateTime
   458  					expectedRule.LastAuditableUpdateUser = "someone@example.com"
   459  
   460  					So(storedRule, ShouldResembleProto, expectedRule)
   461  
   462  					// Verify the returned rule matches what was expected.
   463  					mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
   464  					So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   465  				})
   466  				Convey("Managing bug priority updated", func() {
   467  					request.UpdateMask.Paths = []string{"is_managing_bug_priority"}
   468  					request.Rule.IsManagingBugPriority = false
   469  
   470  					rule, err := srv.Update(ctx, request)
   471  					So(err, ShouldBeNil)
   472  
   473  					storedRule, err := rules.Read(span.Single(ctx), testProject, ruleManaged.RuleID)
   474  					So(err, ShouldBeNil)
   475  
   476  					// Check the rule was updated.
   477  					So(storedRule.LastUpdateTime, ShouldNotEqual, ruleManaged.LastUpdateTime)
   478  
   479  					// Verify the rule was correctly updated in the database:
   480  					// - The requested update should be applied.
   481  					expectedRule := ruleManagedBuilder.Build()
   482  					expectedRule.IsManagingBugPriority = false
   483  
   484  					// - Accept the new last update time (this value is set non-deterministically).
   485  					expectedRule.IsManagingBugPriorityLastUpdateTime = storedRule.LastUpdateTime
   486  					expectedRule.LastUpdateTime = storedRule.LastUpdateTime
   487  					expectedRule.LastAuditableUpdateTime = storedRule.LastUpdateTime
   488  					expectedRule.LastAuditableUpdateUser = "someone@example.com"
   489  
   490  					So(storedRule, ShouldResembleProto, expectedRule)
   491  
   492  					// Verify the returned rule matches what was expected.
   493  					mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
   494  					So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   495  				})
   496  				Convey("Re-use of bug managed by another project", func() {
   497  					request.UpdateMask.Paths = []string{"bug"}
   498  					request.Rule.Bug = &pb.AssociatedBug{
   499  						System: ruleManagedOther.BugID.System,
   500  						Id:     ruleManagedOther.BugID.ID,
   501  					}
   502  
   503  					rule, err := srv.Update(ctx, request)
   504  					So(err, ShouldBeNil)
   505  
   506  					storedRule, err := rules.Read(span.Single(ctx), testProject, ruleManaged.RuleID)
   507  					So(err, ShouldBeNil)
   508  
   509  					// Check the rule was updated.
   510  					So(storedRule.LastUpdateTime, ShouldNotEqual, ruleManaged.LastUpdateTime)
   511  
   512  					// Verify the rule was correctly updated in the database:
   513  					// - The requested update should be applied.
   514  					expectedRule := ruleManagedBuilder.Build()
   515  					expectedRule.BugID = ruleManagedOther.BugID
   516  
   517  					// - The notification flags should be reset as the bug was changed.
   518  					expectedRule.BugManagementState.RuleAssociationNotified = false
   519  					for _, policyState := range expectedRule.BugManagementState.PolicyState {
   520  						policyState.ActivationNotified = false
   521  					}
   522  
   523  					// - IsManagingBug should be silently set to false, because ruleManagedOther
   524  					//  already controls the bug.
   525  					expectedRule.IsManagingBug = false
   526  
   527  					// - Accept the new last update time (this value is set non-deterministically).
   528  					expectedRule.LastUpdateTime = storedRule.LastUpdateTime
   529  					expectedRule.LastAuditableUpdateTime = storedRule.LastUpdateTime
   530  					expectedRule.LastAuditableUpdateUser = "someone@example.com"
   531  
   532  					So(storedRule, ShouldResembleProto, expectedRule)
   533  
   534  					// Verify the returned rule matches what was expected.
   535  					mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
   536  					So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   537  				})
   538  			})
   539  			Convey("Concurrent Modification", func() {
   540  				_, err := srv.Update(ctx, request)
   541  				So(err, ShouldBeNil)
   542  
   543  				// Attempt the same modification again without
   544  				// requerying.
   545  				rule, err := srv.Update(ctx, request)
   546  				So(rule, ShouldBeNil)
   547  				So(err, ShouldBeRPCAborted)
   548  			})
   549  			Convey("Rule does not exist", func() {
   550  				ruleID := strings.Repeat("00", 16)
   551  				request.Rule.Name = fmt.Sprintf("projects/%s/rules/%s", testProject, ruleID)
   552  
   553  				rule, err := srv.Update(ctx, request)
   554  				So(rule, ShouldBeNil)
   555  				So(err, ShouldBeRPCNotFound)
   556  			})
   557  			Convey("Validation error", func() {
   558  				Convey("Invalid bug monorail project", func() {
   559  					request.Rule.Bug.Id = "otherproject/2"
   560  
   561  					rule, err := srv.Update(ctx, request)
   562  					So(rule, ShouldBeNil)
   563  					So(err, ShouldBeRPCInvalidArgument, "bug not in expected monorail project (monorailproject)")
   564  				})
   565  				Convey("Monorail bug on project that does not support it", func() {
   566  					cfg.BugManagement.Monorail = nil
   567  
   568  					err = config.SetTestProjectConfig(ctx, map[string]*configpb.ProjectConfig{
   569  						"testproject": cfg,
   570  					})
   571  
   572  					rule, err := srv.Update(ctx, request)
   573  					So(rule, ShouldBeNil)
   574  					So(err, ShouldBeRPCInvalidArgument, "monorail bug system not enabled for this LUCI project")
   575  				})
   576  				Convey("Re-use of same bug in same project", func() {
   577  					// Use the same bug as another rule.
   578  					request.Rule.Bug = &pb.AssociatedBug{
   579  						System: ruleTwoProject.BugID.System,
   580  						Id:     ruleTwoProject.BugID.ID,
   581  					}
   582  
   583  					rule, err := srv.Update(ctx, request)
   584  					So(rule, ShouldBeNil)
   585  					So(err, ShouldBeRPCInvalidArgument,
   586  						fmt.Sprintf("bug already used by a rule in the same project (%s/%s)",
   587  							ruleTwoProject.Project, ruleTwoProject.RuleID))
   588  				})
   589  				Convey("Bug managed by another rule", func() {
   590  					// Select a bug already managed by another rule.
   591  					request.Rule.Bug = &pb.AssociatedBug{
   592  						System: ruleManagedOther.BugID.System,
   593  						Id:     ruleManagedOther.BugID.ID,
   594  					}
   595  					// Request we manage this bug.
   596  					request.Rule.IsManagingBug = true
   597  					request.UpdateMask.Paths = []string{"bug", "is_managing_bug"}
   598  
   599  					rule, err := srv.Update(ctx, request)
   600  					So(rule, ShouldBeNil)
   601  					So(err, ShouldBeRPCInvalidArgument,
   602  						fmt.Sprintf("bug already managed by a rule in another project (%s/%s)",
   603  							ruleManagedOther.Project, ruleManagedOther.RuleID))
   604  				})
   605  				Convey("Invalid rule definition", func() {
   606  					// Use an invalid failure association rule.
   607  					request.Rule.RuleDefinition = ""
   608  
   609  					rule, err := srv.Update(ctx, request)
   610  					So(rule, ShouldBeNil)
   611  					So(err, ShouldBeRPCInvalidArgument, `rule definition: syntax error: 1:1: unexpected token "<EOF>"`)
   612  				})
   613  			})
   614  		})
   615  		Convey("Create", func() {
   616  			authState.IdentityPermissions = []authtest.RealmPermission{
   617  				{
   618  					Realm:      "testproject:@project",
   619  					Permission: perms.PermCreateRule,
   620  				},
   621  				{
   622  					Realm:      "testproject:@project",
   623  					Permission: perms.PermGetRuleDefinition,
   624  				},
   625  			}
   626  			request := &pb.CreateRuleRequest{
   627  				Parent: fmt.Sprintf("projects/%s", testProject),
   628  				Rule: &pb.Rule{
   629  					RuleDefinition: `test = "create"`,
   630  					Bug: &pb.AssociatedBug{
   631  						System: "monorail",
   632  						Id:     "monorailproject/2",
   633  					},
   634  					IsActive:              false,
   635  					IsManagingBug:         true,
   636  					IsManagingBugPriority: true,
   637  					SourceCluster: &pb.ClusterId{
   638  						Algorithm: testname.AlgorithmName,
   639  						Id:        strings.Repeat("aa", 16),
   640  					},
   641  				},
   642  			}
   643  
   644  			Convey("No create rule permission", func() {
   645  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermCreateRule)
   646  
   647  				rule, err := srv.Create(ctx, request)
   648  				So(err, ShouldBeRPCPermissionDenied, "caller does not have permission analysis.rules.create")
   649  				So(rule, ShouldBeNil)
   650  			})
   651  			Convey("No create rule definition permission", func() {
   652  				authState.IdentityPermissions = removePermission(authState.IdentityPermissions, perms.PermGetRuleDefinition)
   653  
   654  				rule, err := srv.Create(ctx, request)
   655  				So(err, ShouldBeRPCPermissionDenied, "caller does not have permission analysis.rules.getDefinition")
   656  				So(rule, ShouldBeNil)
   657  			})
   658  			Convey("Success", func() {
   659  				expectedRuleBuilder := rules.NewRule(0).
   660  					WithProject(testProject).
   661  					WithRuleDefinition(`test = "create"`).
   662  					WithActive(false).
   663  					WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/2"}).
   664  					WithBugManaged(true).
   665  					WithBugPriorityManaged(true).
   666  					WithCreateUser("someone@example.com").
   667  					WithLastAuditableUpdateUser("someone@example.com").
   668  					WithSourceCluster(clustering.ClusterID{
   669  						Algorithm: testname.AlgorithmName,
   670  						ID:        strings.Repeat("aa", 16),
   671  					}).
   672  					WithBugManagementState(&bugspb.BugManagementState{})
   673  
   674  				Convey("Bug not managed by another rule", func() {
   675  					// Re-use the same bug as a rule in another project,
   676  					// where the other rule is not managing the bug.
   677  					request.Rule.Bug = &pb.AssociatedBug{
   678  						System: ruleUnmanagedOther.BugID.System,
   679  						Id:     ruleUnmanagedOther.BugID.ID,
   680  					}
   681  
   682  					rule, err := srv.Create(ctx, request)
   683  					So(err, ShouldBeNil)
   684  
   685  					storedRule, err := rules.Read(span.Single(ctx), testProject, rule.RuleId)
   686  					So(err, ShouldBeNil)
   687  
   688  					expectedRule := expectedRuleBuilder.
   689  						// Accept the randomly generated rule ID.
   690  						WithRuleID(rule.RuleId).
   691  						WithBug(ruleUnmanagedOther.BugID).
   692  						// Accept whatever CreationTime was assigned, as it
   693  						// is determined by Spanner commit time.
   694  						// Rule spanner data access code tests already validate
   695  						// this is populated correctly.
   696  						WithCreateTime(storedRule.CreateTime).
   697  						WithLastAuditableUpdateTime(storedRule.CreateTime).
   698  						WithLastUpdateTime(storedRule.CreateTime).
   699  						WithPredicateLastUpdateTime(storedRule.CreateTime).
   700  						WithBugPriorityManagedLastUpdateTime(storedRule.CreateTime).
   701  						Build()
   702  
   703  					// Verify the rule was correctly created in the database.
   704  					So(storedRule, ShouldResembleProto, expectedRuleBuilder.Build())
   705  
   706  					// Verify the returned rule matches our expectations.
   707  					mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
   708  					So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   709  				})
   710  				Convey("Bug managed by another rule", func() {
   711  					// Re-use the same bug as a rule in another project,
   712  					// where that rule is managing the bug.
   713  					request.Rule.Bug = &pb.AssociatedBug{
   714  						System: ruleManagedOther.BugID.System,
   715  						Id:     ruleManagedOther.BugID.ID,
   716  					}
   717  
   718  					rule, err := srv.Create(ctx, request)
   719  					So(err, ShouldBeNil)
   720  
   721  					storedRule, err := rules.Read(span.Single(ctx), testProject, rule.RuleId)
   722  					So(err, ShouldBeNil)
   723  
   724  					expectedRule := expectedRuleBuilder.
   725  						// Accept the randomly generated rule ID.
   726  						WithRuleID(rule.RuleId).
   727  						WithBug(ruleManagedOther.BugID).
   728  						// Because another rule is managing the bug, this rule
   729  						// should be silenlty stopped from managing the bug.
   730  						WithBugManaged(false).
   731  						// Accept whatever CreationTime was assigned.
   732  						WithCreateTime(storedRule.CreateTime).
   733  						WithLastAuditableUpdateTime(storedRule.CreateTime).
   734  						WithLastUpdateTime(storedRule.CreateTime).
   735  						WithPredicateLastUpdateTime(storedRule.CreateTime).
   736  						WithBugPriorityManagedLastUpdateTime(storedRule.CreateTime).
   737  						Build()
   738  
   739  					// Verify the rule was correctly created in the database.
   740  					So(storedRule, ShouldResembleProto, expectedRuleBuilder.Build())
   741  
   742  					// Verify the returned rule matches our expectations.
   743  					mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
   744  					So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   745  				})
   746  				Convey("Buganizer", func() {
   747  					request.Rule.Bug = &pb.AssociatedBug{
   748  						System: "buganizer",
   749  						Id:     "1111111111",
   750  					}
   751  
   752  					rule, err := srv.Create(ctx, request)
   753  					So(err, ShouldBeNil)
   754  
   755  					storedRule, err := rules.Read(span.Single(ctx), testProject, rule.RuleId)
   756  					So(err, ShouldBeNil)
   757  
   758  					expectedRule := expectedRuleBuilder.
   759  						// Accept the randomly generated rule ID.
   760  						WithRuleID(rule.RuleId).
   761  						WithBug(bugs.BugID{System: "buganizer", ID: "1111111111"}).
   762  						// Accept whatever CreationTime was assigned, as it
   763  						// is determined by Spanner commit time.
   764  						// Rule spanner data access code tests already validate
   765  						// this is populated correctly.
   766  						WithCreateTime(storedRule.CreateTime).
   767  						WithLastAuditableUpdateTime(storedRule.CreateTime).
   768  						WithLastUpdateTime(storedRule.CreateTime).
   769  						WithPredicateLastUpdateTime(storedRule.CreateTime).
   770  						WithBugPriorityManagedLastUpdateTime(storedRule.CreateTime).
   771  						Build()
   772  
   773  					// Verify the rule was correctly created in the database.
   774  					So(storedRule, ShouldResembleProto, expectedRuleBuilder.Build())
   775  
   776  					// Verify the returned rule matches our expectations.
   777  					mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
   778  					So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   779  				})
   780  				Convey("No audit users permission", func() {
   781  					authState.IdentityGroups = removeGroup(authState.IdentityGroups, auditUsersAccessGroup)
   782  
   783  					rule, err := srv.Create(ctx, request)
   784  					So(err, ShouldBeNil)
   785  
   786  					storedRule, err := rules.Read(span.Single(ctx), testProject, rule.RuleId)
   787  					So(err, ShouldBeNil)
   788  
   789  					expectedRule := expectedRuleBuilder.
   790  						// Accept the randomly generated rule ID.
   791  						WithRuleID(rule.RuleId).
   792  						// Accept whatever CreationTime was assigned, as it
   793  						// is determined by Spanner commit time.
   794  						// Rule spanner data access code tests already validate
   795  						// this is populated correctly.
   796  						WithCreateTime(storedRule.CreateTime).
   797  						WithLastAuditableUpdateTime(storedRule.CreateTime).
   798  						WithLastUpdateTime(storedRule.CreateTime).
   799  						WithPredicateLastUpdateTime(storedRule.CreateTime).
   800  						WithBugPriorityManagedLastUpdateTime(storedRule.CreateTime).
   801  						Build()
   802  
   803  					// Verify the rule was correctly created in the database.
   804  					So(storedRule, ShouldResembleProto, expectedRuleBuilder.Build())
   805  
   806  					// Verify the returned rule matches our expectations.
   807  					mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: false}
   808  					So(rule, ShouldResembleProto, createRulePB(expectedRule, cfg, mask))
   809  				})
   810  			})
   811  			Convey("Validation error", func() {
   812  				Convey("Invalid bug monorail project", func() {
   813  					request.Rule.Bug.Id = "otherproject/2"
   814  
   815  					rule, err := srv.Create(ctx, request)
   816  					So(rule, ShouldBeNil)
   817  					So(err, ShouldBeRPCInvalidArgument,
   818  						"bug not in expected monorail project (monorailproject)")
   819  				})
   820  				Convey("Re-use of same bug in same project", func() {
   821  					// Use the same bug as another rule, in the same project.
   822  					request.Rule.Bug = &pb.AssociatedBug{
   823  						System: ruleTwoProject.BugID.System,
   824  						Id:     ruleTwoProject.BugID.ID,
   825  					}
   826  
   827  					rule, err := srv.Create(ctx, request)
   828  					So(rule, ShouldBeNil)
   829  					So(err, ShouldBeRPCInvalidArgument,
   830  						fmt.Sprintf("bug already used by a rule in the same project (%s/%s)",
   831  							ruleTwoProject.Project, ruleTwoProject.RuleID))
   832  				})
   833  				Convey("Invalid rule definition", func() {
   834  					// Use an invalid failure association rule.
   835  					request.Rule.RuleDefinition = ""
   836  
   837  					rule, err := srv.Create(ctx, request)
   838  					So(rule, ShouldBeNil)
   839  					So(err, ShouldBeRPCInvalidArgument, `rule definition: syntax error: 1:1: unexpected token "<EOF>"`)
   840  				})
   841  			})
   842  		})
   843  		Convey("LookupBug", func() {
   844  			authState.IdentityPermissions = []authtest.RealmPermission{
   845  				{
   846  					Realm:      "testproject:@project",
   847  					Permission: perms.PermListRules,
   848  				},
   849  				{
   850  					Realm:      "otherproject:@project",
   851  					Permission: perms.PermListRules,
   852  				},
   853  			}
   854  
   855  			Convey("Exists None", func() {
   856  				request := &pb.LookupBugRequest{
   857  					System: "monorail",
   858  					Id:     "notexists/1",
   859  				}
   860  
   861  				response, err := srv.LookupBug(ctx, request)
   862  				So(err, ShouldBeNil)
   863  				So(response, ShouldResembleProto, &pb.LookupBugResponse{
   864  					Rules: []string{},
   865  				})
   866  			})
   867  			Convey("Exists One", func() {
   868  				request := &pb.LookupBugRequest{
   869  					System: ruleManaged.BugID.System,
   870  					Id:     ruleManaged.BugID.ID,
   871  				}
   872  
   873  				response, err := srv.LookupBug(ctx, request)
   874  				So(err, ShouldBeNil)
   875  				So(response, ShouldResembleProto, &pb.LookupBugResponse{
   876  					Rules: []string{
   877  						fmt.Sprintf("projects/%s/rules/%s",
   878  							ruleManaged.Project, ruleManaged.RuleID),
   879  					},
   880  				})
   881  
   882  				Convey("If no permission in relevant project", func() {
   883  					authState.IdentityPermissions = nil
   884  
   885  					response, err := srv.LookupBug(ctx, request)
   886  					So(err, ShouldBeNil)
   887  					So(response, ShouldResembleProto, &pb.LookupBugResponse{
   888  						Rules: []string{},
   889  					})
   890  				})
   891  			})
   892  			Convey("Exists Many", func() {
   893  				request := &pb.LookupBugRequest{
   894  					System: ruleTwoProject.BugID.System,
   895  					Id:     ruleTwoProject.BugID.ID,
   896  				}
   897  
   898  				response, err := srv.LookupBug(ctx, request)
   899  				So(err, ShouldBeNil)
   900  				So(response, ShouldResembleProto, &pb.LookupBugResponse{
   901  					Rules: []string{
   902  						// Rules are returned alphabetically by project.
   903  						fmt.Sprintf("projects/otherproject/rules/%s", ruleTwoProjectOther.RuleID),
   904  						fmt.Sprintf("projects/testproject/rules/%s", ruleTwoProject.RuleID),
   905  					},
   906  				})
   907  
   908  				Convey("If list permission exists in only some projects", func() {
   909  					authState.IdentityPermissions = []authtest.RealmPermission{
   910  						{
   911  							Realm:      "testproject:@project",
   912  							Permission: perms.PermListRules,
   913  						},
   914  					}
   915  
   916  					response, err := srv.LookupBug(ctx, request)
   917  					So(err, ShouldBeNil)
   918  					So(response, ShouldResembleProto, &pb.LookupBugResponse{
   919  						Rules: []string{
   920  							fmt.Sprintf("projects/testproject/rules/%s", ruleTwoProject.RuleID),
   921  						},
   922  					})
   923  				})
   924  			})
   925  		})
   926  	})
   927  	Convey("createRulePB", t, func() {
   928  		// The behaviour of createRulePB is assumed by many test cases.
   929  		// This verifies that behaviour.
   930  
   931  		rule := rules.NewRule(1).
   932  			WithProject(testProject).
   933  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/111"}).Build()
   934  
   935  		expectedRule := &pb.Rule{
   936  			Name:                    "projects/testproject/rules/8109e4f8d9c218e9a2e33a7a21395455",
   937  			Project:                 "testproject",
   938  			RuleId:                  "8109e4f8d9c218e9a2e33a7a21395455",
   939  			RuleDefinition:          "reason LIKE \"%exit code 5%\" AND test LIKE \"tast.arc.%\"",
   940  			IsActive:                true,
   941  			PredicateLastUpdateTime: timestamppb.New(time.Date(1904, 4, 4, 4, 4, 4, 1, time.UTC)),
   942  			Bug: &pb.AssociatedBug{
   943  				System:   "monorail",
   944  				Id:       "monorailproject/111",
   945  				LinkText: "mybug.com/111",
   946  				Url:      "https://monorailhost.com/p/monorailproject/issues/detail?id=111",
   947  			},
   948  			IsManagingBug:                       true,
   949  			IsManagingBugPriority:               true,
   950  			IsManagingBugPriorityLastUpdateTime: timestamppb.New(time.Date(1905, 5, 5, 5, 5, 5, 1, time.UTC)),
   951  			SourceCluster: &pb.ClusterId{
   952  				Algorithm: "clusteralg1-v9",
   953  				Id:        "696431",
   954  			},
   955  			BugManagementState: &pb.BugManagementState{
   956  				PolicyState: []*pb.BugManagementState_PolicyState{
   957  					{
   958  						PolicyId:           "policy-a",
   959  						IsActive:           true,
   960  						LastActivationTime: timestamppb.New(time.Date(1908, 8, 8, 8, 8, 8, 1, time.UTC)),
   961  					},
   962  				},
   963  			},
   964  			CreateTime:              timestamppb.New(time.Date(1900, 1, 2, 3, 4, 5, 1, time.UTC)),
   965  			CreateUser:              "system",
   966  			LastAuditableUpdateTime: timestamppb.New(time.Date(1907, 7, 7, 7, 7, 7, 1, time.UTC)), //FIX ME
   967  			LastAuditableUpdateUser: "user@google.com",
   968  			LastUpdateTime:          timestamppb.New(time.Date(1909, 9, 9, 9, 9, 9, 1, time.UTC)),
   969  			Etag:                    `W/"+d+u/1909-09-09T09:09:09.000000001Z"`,
   970  		}
   971  		cfg := &configpb.ProjectConfig{
   972  			BugManagement: &configpb.BugManagement{
   973  				Monorail: &configpb.MonorailProject{
   974  					Project:          "monorailproject",
   975  					DisplayPrefix:    "mybug.com",
   976  					MonorailHostname: "monorailhost.com",
   977  				},
   978  			},
   979  		}
   980  		mask := ruleMask{
   981  			IncludeDefinition: true,
   982  			IncludeAuditUsers: true,
   983  		}
   984  		Convey("With all fields", func() {
   985  			So(createRulePB(rule, cfg, mask), ShouldResembleProto, expectedRule)
   986  		})
   987  		Convey("Without definition field", func() {
   988  			mask.IncludeDefinition = false
   989  			expectedRule.RuleDefinition = ""
   990  			expectedRule.Etag = `W/"+u/1909-09-09T09:09:09.000000001Z"`
   991  			So(createRulePB(rule, cfg, mask), ShouldResembleProto, expectedRule)
   992  		})
   993  		Convey("Without audit users", func() {
   994  			mask.IncludeAuditUsers = false
   995  			expectedRule.CreateUser = ""
   996  			expectedRule.LastAuditableUpdateUser = ""
   997  			expectedRule.Etag = `W/"+d/1909-09-09T09:09:09.000000001Z"`
   998  			So(createRulePB(rule, cfg, mask), ShouldResembleProto, expectedRule)
   999  		})
  1000  	})
  1001  	Convey("isETagValid", t, func() {
  1002  		rule := rules.NewRule(0).
  1003  			WithProject(testProject).
  1004  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/111"}).Build()
  1005  
  1006  		Convey("Should match ETags for same rule version", func() {
  1007  			masks := []ruleMask{
  1008  				{IncludeDefinition: true, IncludeAuditUsers: true},
  1009  				{IncludeDefinition: true, IncludeAuditUsers: false},
  1010  				{IncludeDefinition: false, IncludeAuditUsers: true},
  1011  				{IncludeDefinition: false, IncludeAuditUsers: false},
  1012  			}
  1013  			for _, mask := range masks {
  1014  				So(isETagMatching(rule, ruleETag(rule, mask)), ShouldBeTrue)
  1015  			}
  1016  		})
  1017  		Convey("Should not match ETag for different version of rule", func() {
  1018  			mask := ruleMask{IncludeDefinition: true, IncludeAuditUsers: true}
  1019  			etag := ruleETag(rule, mask)
  1020  
  1021  			rule.LastUpdateTime = time.Date(2021, 5, 4, 3, 2, 1, 0, time.UTC)
  1022  			So(isETagMatching(rule, etag), ShouldBeFalse)
  1023  		})
  1024  	})
  1025  	Convey("formatRule", t, func() {
  1026  		rule := rules.NewRule(0).
  1027  			WithProject(testProject).
  1028  			WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/123456"}).
  1029  			WithRuleDefinition(`test = "create"`).
  1030  			WithActive(false).
  1031  			WithBugManaged(true).
  1032  			WithSourceCluster(clustering.ClusterID{
  1033  				Algorithm: testname.AlgorithmName,
  1034  				ID:        strings.Repeat("aa", 16),
  1035  			}).Build()
  1036  		expectedRule := `{
  1037  	RuleDefinition: "test = \"create\"",
  1038  	BugID: "monorail:monorailproject/123456",
  1039  	IsActive: false,
  1040  	IsManagingBug: true,
  1041  	IsManagingBugPriority: true,
  1042  	SourceCluster: "testname-v4:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  1043  	LastAuditableUpdate: "1907-07-07T07:07:07Z"
  1044  	LastUpdated: "1909-09-09T09:09:09Z"
  1045  }`
  1046  		So(formatRule(rule), ShouldEqual, expectedRule)
  1047  	})
  1048  }
  1049  
  1050  func removeGroup(groups []string, group string) []string {
  1051  	var result []string
  1052  	for _, g := range groups {
  1053  		if g != group {
  1054  			result = append(result, g)
  1055  		}
  1056  	}
  1057  	return result
  1058  }