go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/configs/prjcfg/loader_test.go (about) 1 // Copyright 2020 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 prjcfg 16 17 import ( 18 "context" 19 "testing" 20 21 "go.chromium.org/luci/gae/service/datastore" 22 23 cfgpb "go.chromium.org/luci/cv/api/config/v2" 24 "go.chromium.org/luci/cv/internal/cvtesting" 25 26 . "github.com/smartystreets/goconvey/convey" 27 . "go.chromium.org/luci/common/testing/assertions" 28 ) 29 30 func mockUpdateProjectConfig(ctx context.Context, project string, cgs []*cfgpb.ConfigGroup) error { 31 const cfgHash = "sha256:deadbeef" 32 var cfgGroups []*ConfigGroup 33 var cgNames []string 34 for _, cg := range cgs { 35 cfgGroups = append(cfgGroups, &ConfigGroup{ 36 ID: MakeConfigGroupID(cfgHash, cg.Name), 37 Project: datastore.MakeKey(ctx, projectConfigKind, project), 38 Content: cg, 39 }) 40 cgNames = append(cgNames, cg.Name) 41 } 42 pc := &ProjectConfig{ 43 Project: project, 44 Enabled: true, 45 Hash: cfgHash, 46 ExternalHash: cfgHash + "external", 47 ConfigGroupNames: cgNames, 48 SchemaVersion: SchemaVersion, 49 } 50 cfgHashInfo := &ConfigHashInfo{ 51 Hash: cfgHash, 52 Project: datastore.MakeKey(ctx, projectConfigKind, project), 53 ConfigGroupNames: cgNames, 54 GitRevision: "abcdef123456", 55 } 56 return datastore.Put(ctx, pc, cfgHashInfo, cfgGroups) 57 } 58 59 func mockDisableProjectConfig(ctx context.Context, project string, cgs []*cfgpb.ConfigGroup) error { 60 const cfgHash = "sha256:deadbeef" 61 var cgNames []string 62 for _, cg := range cgs { 63 cgNames = append(cgNames, cg.Name) 64 } 65 pc := &ProjectConfig{ 66 Project: project, 67 Enabled: false, 68 Hash: cfgHash, 69 ExternalHash: cfgHash + "external", 70 ConfigGroupNames: cgNames, 71 SchemaVersion: SchemaVersion, 72 } 73 return datastore.Put(ctx, pc) 74 } 75 76 func TestLoadingConfigs(t *testing.T) { 77 t.Parallel() 78 Convey("Load project config works", t, func() { 79 ct := cvtesting.Test{} 80 ctx, cancel := ct.SetUp(t) 81 defer cancel() 82 83 const project = "chromium" 84 cfgGroups := []*cfgpb.ConfigGroup{ 85 { 86 Name: "branch_m100", 87 Gerrit: []*cfgpb.ConfigGroup_Gerrit{ 88 { 89 Url: "https://chromium-review.googlesource.com/", 90 Projects: []*cfgpb.ConfigGroup_Gerrit_Project{ 91 { 92 Name: "chromium/src", 93 RefRegexp: []string{"refs/heads/branch_m100"}, 94 }, 95 }, 96 }, 97 }, 98 }, 99 { 100 Name: "main", 101 Gerrit: []*cfgpb.ConfigGroup_Gerrit{ 102 { 103 Url: "https://chromium-review.googlesource.com/", 104 Projects: []*cfgpb.ConfigGroup_Gerrit_Project{ 105 { 106 Name: "chromium/src", 107 RefRegexp: []string{"refs/heads/main"}, 108 }, 109 }, 110 }, 111 }, 112 }, 113 } 114 115 So(mockUpdateProjectConfig(ctx, project, cfgGroups), ShouldBeNil) 116 117 Convey("Not existing project", func() { 118 m, err := GetLatestMeta(ctx, "not_chromium") 119 So(err, ShouldBeNil) 120 So(m.Exists(), ShouldBeFalse) 121 So(func() { m.Hash() }, ShouldPanic) 122 }) 123 124 Convey("Enabled project", func() { 125 m, err := GetLatestMeta(ctx, project) 126 So(err, ShouldBeNil) 127 So(m.Exists(), ShouldBeTrue) 128 So(m.Status, ShouldEqual, StatusEnabled) 129 So(m.ConfigGroupNames, ShouldResemble, []string{cfgGroups[0].Name, cfgGroups[1].Name}) 130 So(m.ConfigGroupIDs, ShouldResemble, []ConfigGroupID{ 131 ConfigGroupID(m.Hash() + "/" + cfgGroups[0].Name), 132 ConfigGroupID(m.Hash() + "/" + cfgGroups[1].Name), 133 }) 134 135 m2, err := GetHashMeta(ctx, project, m.Hash()) 136 So(err, ShouldBeNil) 137 So(m2, ShouldResemble, m) 138 139 cgs, err := m.GetConfigGroups(ctx) 140 So(err, ShouldBeNil) 141 So(len(cgs), ShouldEqual, 2) 142 So(cgs[0].Project.StringID(), ShouldEqual, project) 143 So(cgs[0].Content, ShouldResembleProto, cfgGroups[0]) 144 So(cgs[1].Project.StringID(), ShouldEqual, project) 145 So(cgs[1].Content, ShouldResembleProto, cfgGroups[1]) 146 }) 147 148 cfgGroups = append(cfgGroups, &cfgpb.ConfigGroup{ 149 Name: "branch_m200", 150 Gerrit: []*cfgpb.ConfigGroup_Gerrit{ 151 { 152 Url: "https://chromium-review.googlesource.com/", 153 Projects: []*cfgpb.ConfigGroup_Gerrit_Project{ 154 { 155 Name: "chromium/src", 156 RefRegexp: []string{"refs/heads/branch_m200"}, 157 }, 158 }, 159 }, 160 }, 161 }) 162 So(mockUpdateProjectConfig(ctx, project, cfgGroups), ShouldBeNil) 163 164 Convey("Updated project", func() { 165 m, err := GetLatestMeta(ctx, project) 166 So(err, ShouldBeNil) 167 So(m.Exists(), ShouldBeTrue) 168 So(m.Status, ShouldEqual, StatusEnabled) 169 h := m.Hash() 170 So(h, ShouldStartWith, "sha256:") 171 So(m.ConfigGroupIDs, ShouldResemble, []ConfigGroupID{ 172 ConfigGroupID(h + "/" + cfgGroups[0].Name), 173 ConfigGroupID(h + "/" + cfgGroups[1].Name), 174 ConfigGroupID(h + "/" + cfgGroups[2].Name), 175 }) 176 cgs, err := m.GetConfigGroups(ctx) 177 So(err, ShouldBeNil) 178 So(len(cgs), ShouldEqual, 3) 179 180 Convey("reading ConfigGroup directly works", func() { 181 cg, err := GetConfigGroup(ctx, project, m.ConfigGroupIDs[2]) 182 So(err, ShouldBeNil) 183 So(cg.Content, ShouldResembleProto, cfgGroups[2]) 184 }) 185 }) 186 187 So(mockDisableProjectConfig(ctx, project, cfgGroups), ShouldBeNil) 188 189 Convey("Disabled project", func() { 190 m, err := GetLatestMeta(ctx, project) 191 So(err, ShouldBeNil) 192 So(m.Exists(), ShouldBeTrue) 193 So(m.Status, ShouldEqual, StatusDisabled) 194 So(len(m.ConfigGroupIDs), ShouldEqual, 3) 195 cgs, err := m.GetConfigGroups(ctx) 196 So(err, ShouldBeNil) 197 So(len(cgs), ShouldEqual, 3) 198 }) 199 200 // Re-enable the project. 201 So(mockUpdateProjectConfig(ctx, project, cfgGroups), ShouldBeNil) 202 203 Convey("Re-enabled project", func() { 204 m, err := GetLatestMeta(ctx, project) 205 So(err, ShouldBeNil) 206 So(m.Exists(), ShouldBeTrue) 207 So(m.Status, ShouldEqual, StatusEnabled) 208 }) 209 210 m, err := GetLatestMeta(ctx, project) 211 So(err, ShouldBeNil) 212 cgs, err := m.GetConfigGroups(ctx) 213 So(err, ShouldBeNil) 214 215 Convey("Deleted project", func() { 216 So(datastore.Delete(ctx, &ProjectConfig{Project: project}, cgs), ShouldBeNil) 217 218 m, err = GetLatestMeta(ctx, project) 219 So(err, ShouldBeNil) 220 So(m.Exists(), ShouldBeFalse) 221 }) 222 223 Convey("reading partially deleted project", func() { 224 So(datastore.Delete(ctx, cgs[1]), ShouldBeNil) 225 _, err = m.GetConfigGroups(ctx) 226 So(err, ShouldErrLike, "ConfigGroups for") 227 So(err, ShouldErrLike, "not found") 228 So(datastore.IsErrNoSuchEntity(err), ShouldBeTrue) 229 230 // Can still read individual ConfigGroups. 231 cg, err := GetConfigGroup(ctx, project, m.ConfigGroupIDs[0]) 232 So(err, ShouldBeNil) 233 So(cg.Content, ShouldResembleProto, cfgGroups[0]) 234 cg, err = GetConfigGroup(ctx, project, m.ConfigGroupIDs[2]) 235 So(err, ShouldBeNil) 236 So(cg.Content, ShouldResembleProto, cfgGroups[2]) 237 // ... except the deleted one. 238 _, err = GetConfigGroup(ctx, project, m.ConfigGroupIDs[1]) 239 So(datastore.IsErrNoSuchEntity(err), ShouldBeTrue) 240 }) 241 }) 242 }