go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/bugs/monorail/migration/migrate_test.go (about) 1 // Copyright 2024 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 migration 16 17 import ( 18 "sort" 19 "testing" 20 "time" 21 22 "go.chromium.org/luci/gae/impl/memory" 23 "go.chromium.org/luci/server/auth" 24 "go.chromium.org/luci/server/auth/authtest" 25 "go.chromium.org/luci/server/secrets" 26 "go.chromium.org/luci/server/secrets/testsecrets" 27 "go.chromium.org/luci/server/span" 28 29 "go.chromium.org/luci/analysis/internal/bugs" 30 "go.chromium.org/luci/analysis/internal/bugs/monorail" 31 "go.chromium.org/luci/analysis/internal/bugs/monorail/api_proto" 32 migrationpb "go.chromium.org/luci/analysis/internal/bugs/monorail/migration/proto" 33 "go.chromium.org/luci/analysis/internal/clustering/rules" 34 "go.chromium.org/luci/analysis/internal/config" 35 "go.chromium.org/luci/analysis/internal/testutil" 36 configpb "go.chromium.org/luci/analysis/proto/config" 37 38 . "github.com/smartystreets/goconvey/convey" 39 . "go.chromium.org/luci/common/testing/assertions" 40 ) 41 42 func TestMigration(t *testing.T) { 43 Convey("With Server", t, func() { 44 ctx := testutil.IntegrationTestContext(t) 45 46 // For user identification. 47 ctx = authtest.MockAuthConfig(ctx) 48 authState := &authtest.FakeState{ 49 Identity: "user:someone@example.com", 50 IdentityGroups: []string{luciAnalysisAdminGroup}, 51 } 52 ctx = auth.WithState(ctx, authState) 53 ctx = secrets.Use(ctx, &testsecrets.Store{}) 54 55 // Service config required by implementation. 56 ctx = memory.Use(ctx) 57 err := config.SetTestConfig(ctx, &configpb.Config{}) 58 So(err, ShouldBeNil) 59 60 // Set up some rules for testing. 61 ruleMonorail := rules.NewRule(0). 62 WithProject("testproject"). 63 WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/111"}).Build() 64 ruleMonorail2 := rules.NewRule(1). 65 WithProject("testproject"). 66 WithBug(bugs.BugID{System: "monorail", ID: "monorailproject/222"}).Build() 67 ruleBuganizer := rules.NewRule(2). 68 WithProject("testproject"). 69 WithBug(bugs.BugID{System: "buganizer", ID: "666"}). 70 Build() 71 ruleOtherProject := rules.NewRule(3). 72 WithProject("anotherproject"). 73 WithBug(bugs.BugID{System: "monorail", ID: "aproject/111"}). 74 Build() 75 76 err = rules.SetForTesting(ctx, []*rules.Entry{ 77 ruleMonorail, 78 ruleMonorail2, 79 ruleBuganizer, 80 ruleOtherProject, 81 }) 82 So(err, ShouldBeNil) 83 84 f := &monorail.FakeIssuesStore{ 85 NextID: 100, 86 PriorityFieldName: "projects/chromium/fieldDefs/11", 87 ComponentNames: []string{ 88 "projects/chromium/componentDefs/Blink", 89 }, 90 Issues: []*monorail.IssueData{ 91 { 92 Issue: &api_proto.Issue{ 93 Name: "projects/monorailproject/issues/111", 94 MigratedId: "8111", 95 }, 96 }, 97 { 98 Issue: &api_proto.Issue{ 99 Name: "projects/monorailproject/issues/222", 100 MigratedId: "8222", 101 }, 102 }, 103 }, 104 } 105 106 ctx = monorail.UseFakeIssuesClient(ctx, f, "service-user") 107 108 srv := NewMonorailMigrationServer() 109 110 Convey("Unauthorised requests are rejected", func() { 111 // Ensure no access to luci-analysis-access. 112 ctx = auth.WithState(ctx, &authtest.FakeState{ 113 Identity: "user:someone@example.com", 114 // Not a member of service-luci-analysis-admins. 115 IdentityGroups: []string{"other-group"}, 116 }) 117 118 // Make some request (the request should not matter, as 119 // a common decorator is used for all requests.) 120 request := &migrationpb.MigrateProjectRequest{ 121 Project: "testproject", 122 MaxRules: 1, 123 } 124 125 rsp, err := srv.MigrateProject(ctx, request) 126 So(err, ShouldBeRPCPermissionDenied, "not a member of service-luci-analysis-admins") 127 So(rsp, ShouldBeNil) 128 }) 129 Convey("MigrateProject", func() { 130 request := &migrationpb.MigrateProjectRequest{ 131 Project: "testproject", 132 MaxRules: 1, 133 } 134 expectedRules := []*rules.Entry{ 135 ruleMonorail, 136 ruleMonorail2, 137 ruleBuganizer, 138 ruleOtherProject, 139 } 140 141 Convey("Migrated-to bug mapping available", func() { 142 rsp, err := srv.MigrateProject(ctx, request) 143 So(err, ShouldBeNil) 144 So(rsp, ShouldResembleProto, &migrationpb.MigrateProjectResponse{ 145 RulesNotStarted: 0, 146 RuleResults: []*migrationpb.MigrateProjectResponse_RuleResult{ 147 { 148 RuleId: ruleMonorail.RuleID, 149 MonorailBugId: "monorailproject/111", 150 BuganizerBugId: "8111", 151 }, 152 }, 153 }) 154 155 // Rule should be updated to use buganizer bug. 156 expectedRules[0].BugID = bugs.BugID{ 157 System: "buganizer", 158 ID: "8111", 159 } 160 expectedRules[0].BugManagementState.RuleAssociationNotified = false 161 expectedRules[0].LastAuditableUpdateUser = "system" 162 163 rs, err := rules.ReadAllForTesting(span.Single(ctx)) 164 So(err, ShouldBeNil) 165 So(sortRulesAndClearTimestamps(rs), ShouldResembleProto, sortRulesAndClearTimestamps(expectedRules)) 166 }) 167 Convey("Migrated to bug mapping unavailable", func() { 168 for _, issue := range f.Issues { 169 issue.Issue.MigratedId = "" 170 } 171 172 rsp, err := srv.MigrateProject(ctx, request) 173 So(err, ShouldBeNil) 174 So(rsp, ShouldResembleProto, &migrationpb.MigrateProjectResponse{ 175 RulesNotStarted: 0, 176 RuleResults: []*migrationpb.MigrateProjectResponse_RuleResult{ 177 { 178 RuleId: ruleMonorail.RuleID, 179 MonorailBugId: "monorailproject/111", 180 BuganizerBugId: "", 181 Error: "monorail did not return the Buganizer bug this bug was migrated to", 182 }, 183 }, 184 }) 185 186 rs, err := rules.ReadAllForTesting(span.Single(ctx)) 187 So(err, ShouldBeNil) 188 So(sortRulesAndClearTimestamps(rs), ShouldResembleProto, sortRulesAndClearTimestamps(expectedRules)) 189 }) 190 }) 191 }) 192 } 193 194 func sortRulesAndClearTimestamps(rs []*rules.Entry) []*rules.Entry { 195 rsCopy := make([]*rules.Entry, len(rs)) 196 copy(rsCopy, rs) 197 198 for _, r := range rsCopy { 199 r.LastUpdateTime = time.Time{} 200 r.LastAuditableUpdateTime = time.Time{} 201 } 202 203 sort.Slice(rsCopy, func(i, j int) bool { 204 return rsCopy[i].RuleID < rsCopy[j].RuleID 205 }) 206 return rsCopy 207 }