go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/common/common_test.go (about) 1 // Copyright 2014 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 common 16 17 import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 api "go.chromium.org/luci/cipd/api/cipd/v1" 23 24 . "github.com/smartystreets/goconvey/convey" 25 . "go.chromium.org/luci/common/testing/assertions" 26 ) 27 28 func TestValidatePackageName(t *testing.T) { 29 t.Parallel() 30 31 Convey("ValidatePackageName works", t, func() { 32 So(ValidatePackageName("good/name"), ShouldBeNil) 33 So(ValidatePackageName("good_name"), ShouldBeNil) 34 So(ValidatePackageName("123-_/also/good/name"), ShouldBeNil) 35 So(ValidatePackageName("good.name/.name/..name"), ShouldBeNil) 36 So(ValidatePackageName(""), ShouldNotBeNil) 37 So(ValidatePackageName("/"), ShouldNotBeNil) 38 So(ValidatePackageName("BAD/name"), ShouldNotBeNil) 39 So(ValidatePackageName("bad//name"), ShouldNotBeNil) 40 So(ValidatePackageName("bad/name/"), ShouldNotBeNil) 41 So(ValidatePackageName("/bad/name"), ShouldNotBeNil) 42 So(ValidatePackageName("bad/name\nyeah"), ShouldNotBeNil) 43 So(ValidatePackageName("./name"), ShouldNotBeNil) 44 So(ValidatePackageName("name/../name"), ShouldNotBeNil) 45 So(ValidatePackageName("../../yeah"), ShouldNotBeNil) 46 So(ValidatePackageName("..."), ShouldNotBeNil) 47 }) 48 } 49 50 func TestValidatePackagePrefix(t *testing.T) { 51 t.Parallel() 52 53 Convey("ValidatePackagePrefix strips suffix", t, func() { 54 p, err := ValidatePackagePrefix("good/name/") 55 So(err, ShouldBeNil) 56 So(p, ShouldEqual, "good/name") 57 }) 58 59 Convey("ValidatePackagePrefix works", t, func() { 60 call := func(p string) error { 61 _, err := ValidatePackagePrefix(p) 62 return err 63 } 64 65 So(call("good/name"), ShouldBeNil) 66 So(call("good/name/"), ShouldBeNil) 67 So(call("good_name"), ShouldBeNil) 68 So(call("123-_/also/good/name"), ShouldBeNil) 69 So(call("good.name/.name/..name"), ShouldBeNil) 70 So(call(""), ShouldBeNil) // repo root 71 So(call("/"), ShouldBeNil) // repo root 72 So(call("BAD/name"), ShouldNotBeNil) 73 So(call("bad//name"), ShouldNotBeNil) 74 So(call("bad/name//"), ShouldNotBeNil) 75 So(call("/bad/name"), ShouldNotBeNil) 76 So(call("bad/name\nyeah"), ShouldNotBeNil) 77 So(call("./name"), ShouldNotBeNil) 78 So(call("name/../name"), ShouldNotBeNil) 79 So(call("../../yeah"), ShouldNotBeNil) 80 So(call("..."), ShouldNotBeNil) 81 }) 82 } 83 84 func TestValidateInstanceTag(t *testing.T) { 85 t.Parallel() 86 87 Convey("ValidateInstanceTag works", t, func() { 88 So(ValidateInstanceTag(""), ShouldNotBeNil) 89 So(ValidateInstanceTag("notapair"), ShouldNotBeNil) 90 So(ValidateInstanceTag(strings.Repeat("long", 200)+":abc"), ShouldNotBeNil) 91 So(ValidateInstanceTag("BADKEY:value"), ShouldNotBeNil) 92 So(ValidateInstanceTag("empty_val:"), ShouldNotBeNil) 93 So(ValidateInstanceTag(" space:a"), ShouldNotBeNil) 94 So(ValidateInstanceTag("space :a"), ShouldNotBeNil) 95 So(ValidateInstanceTag("space: a"), ShouldNotBeNil) 96 So(ValidateInstanceTag("space:a "), ShouldNotBeNil) 97 So(ValidateInstanceTag("newline:a\n"), ShouldNotBeNil) 98 So(ValidateInstanceTag("tab:a\tb"), ShouldNotBeNil) 99 So(ValidateInstanceTag("good:tag"), ShouldBeNil) 100 So(ValidateInstanceTag("good:tag:blah"), ShouldBeNil) 101 So(ValidateInstanceTag("good_tag:A a0$()*+,-./:;<=>@\\_{}~"), ShouldBeNil) 102 }) 103 } 104 105 func TestParseInstanceTag(t *testing.T) { 106 t.Parallel() 107 108 Convey("ParseInstanceTag works", t, func() { 109 t, err := ParseInstanceTag("good:tag") 110 So(err, ShouldBeNil) 111 So(t, ShouldResembleProto, &api.Tag{ 112 Key: "good", 113 Value: "tag", 114 }) 115 116 t, err = ParseInstanceTag("good:tag:blah") 117 So(err, ShouldBeNil) 118 So(t, ShouldResembleProto, &api.Tag{ 119 Key: "good", 120 Value: "tag:blah", 121 }) 122 123 t, err = ParseInstanceTag("good_tag:A a0$()*+,-./:;<=>@\\_{}~") 124 So(err, ShouldBeNil) 125 So(t, ShouldResembleProto, &api.Tag{ 126 Key: "good_tag", 127 Value: "A a0$()*+,-./:;<=>@\\_{}~", 128 }) 129 130 t, err = ParseInstanceTag("") 131 So(err, ShouldNotBeNil) 132 133 t, err = ParseInstanceTag("notapair") 134 So(err, ShouldNotBeNil) 135 136 t, err = ParseInstanceTag(strings.Repeat("long", 200) + ":abc") 137 So(err, ShouldNotBeNil) 138 139 t, err = ParseInstanceTag("BADKEY:value") 140 So(err, ShouldNotBeNil) 141 }) 142 143 Convey("MustParseInstanceTag panics on bad tag", t, func() { 144 So(func() { MustParseInstanceTag("") }, ShouldPanic) 145 }) 146 } 147 148 func TestValidatePin(t *testing.T) { 149 t.Parallel() 150 151 Convey("ValidatePin works", t, func() { 152 So(ValidatePin(Pin{"good/name", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, KnownHash), ShouldBeNil) 153 So(ValidatePin(Pin{"BAD/name", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, KnownHash), ShouldNotBeNil) 154 So(ValidatePin(Pin{"good/name", "aaaaaaaaaaa"}, KnownHash), ShouldNotBeNil) 155 }) 156 } 157 158 func TestValidatePackageRef(t *testing.T) { 159 t.Parallel() 160 161 Convey("ValidatePackageRef works", t, func() { 162 So(ValidatePackageRef("some-ref"), ShouldBeNil) 163 So(ValidatePackageRef("ref/with/slashes.and.dots"), ShouldBeNil) 164 165 So(ValidatePackageRef(""), ShouldNotBeNil) 166 So(ValidatePackageRef("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), ShouldNotBeNil) 167 So(ValidatePackageRef("good:tag"), ShouldNotBeNil) 168 }) 169 } 170 171 func TestValidateInstanceVersion(t *testing.T) { 172 t.Parallel() 173 174 Convey("ValidateInstanceVersion works", t, func() { 175 So(ValidateInstanceVersion("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), ShouldBeNil) 176 So(ValidateInstanceVersion("good:tag"), ShouldBeNil) 177 So(ValidatePackageRef("some-read"), ShouldBeNil) 178 So(ValidateInstanceVersion("BADTAG:"), ShouldNotBeNil) 179 }) 180 } 181 182 func TestValidateSubdir(t *testing.T) { 183 t.Parallel() 184 185 badSubdirs := []struct { 186 name string 187 subdir string 188 err string 189 }{ 190 {"windows", "folder\\thing", "backslashes are not allowed"}, 191 {"windows drive", "c:/foo/bar", `colons are not allowed`}, 192 {"messy", "some/../thing", `"some/../thing": should be simplified to "thing"`}, 193 {"relative", "../something", `contains disallowed dot-path prefix`}, 194 {"single relative", "./something", `"./something": should be simplified to "something"`}, 195 {"absolute", "/etc", `absolute paths are not allowed`}, 196 {"extra slashes", "//foo/bar", `bad subdir`}, 197 } 198 199 goodSubdirs := []struct { 200 name string 201 subdir string 202 }{ 203 {"empty", ""}, 204 {"simple path", "some/path"}, 205 {"single path", "something"}, 206 {"spaces", "some path/with/ spaces"}, 207 } 208 209 Convey("ValidtateSubdir", t, func() { 210 Convey("rejects bad subdirs", func() { 211 for _, tc := range badSubdirs { 212 Convey(tc.name, func() { 213 So(ValidateSubdir(tc.subdir), ShouldErrLike, tc.err) 214 }) 215 } 216 }) 217 218 Convey("accepts good subdirs", func() { 219 for _, tc := range goodSubdirs { 220 Convey(tc.name, func() { 221 So(ValidateSubdir(tc.subdir), ShouldErrLike, nil) 222 }) 223 } 224 }) 225 }) 226 } 227 228 func TestValidatePrincipalName(t *testing.T) { 229 t.Parallel() 230 231 Convey("ValidatePrincipalName OK", t, func() { 232 cases := []string{ 233 "group:abc", 234 "user:a@example.com", 235 "anonymous:anonymous", 236 "bot:blah", 237 "service:blah", 238 } 239 for _, tc := range cases { 240 So(ValidatePrincipalName(tc), ShouldBeNil) 241 } 242 }) 243 244 Convey("ValidatePrincipalName not OK", t, func() { 245 cases := []struct{ p, err string }{ 246 {"", "doesn't look like a principal id"}, 247 {":", "doesn't look like a principal id"}, 248 {":zzz", "doesn't look like a principal id"}, 249 {"group:", "doesn't look like a principal id"}, 250 {"user:", "doesn't look like a principal id"}, 251 {"anonymous:zzz", `bad value "zzz" for identity kind "anonymous"`}, 252 {"user:abc", `bad value "abc" for identity kind "user"`}, 253 } 254 for _, tc := range cases { 255 So(ValidatePrincipalName(tc.p), ShouldErrLike, tc.err) 256 } 257 }) 258 } 259 260 func TestNormalizePrefixMetadata(t *testing.T) { 261 t.Parallel() 262 263 Convey("Happy path", t, func() { 264 m := &api.PrefixMetadata{ 265 Prefix: "abc/", 266 Acls: []*api.PrefixMetadata_ACL{ 267 {Role: api.Role_OWNER, Principals: []string{"user:abc@example.com", "group:a"}}, 268 {Role: api.Role_READER, Principals: []string{"group:z"}}, 269 {Role: 123}, // some future unknown role 270 }, 271 } 272 So(NormalizePrefixMetadata(m), ShouldBeNil) 273 So(m, ShouldResembleProto, &api.PrefixMetadata{ 274 Prefix: "abc", 275 Acls: []*api.PrefixMetadata_ACL{ 276 {Role: api.Role_READER, Principals: []string{"group:z"}}, 277 {Role: api.Role_OWNER, Principals: []string{"group:a", "user:abc@example.com"}}, 278 {Role: 123}, 279 }, 280 }) 281 }) 282 283 Convey("Validates prefix", t, func() { 284 So(NormalizePrefixMetadata(&api.PrefixMetadata{Prefix: "//"}), 285 ShouldErrLike, "invalid package prefix") 286 }) 287 288 Convey("No role", t, func() { 289 So(NormalizePrefixMetadata(&api.PrefixMetadata{ 290 Prefix: "abc", 291 Acls: []*api.PrefixMetadata_ACL{ 292 {}, 293 }, 294 }), ShouldErrLike, "ACL entry #0 doesn't have a role specified") 295 }) 296 297 Convey("Double ACL entries", t, func() { 298 So(NormalizePrefixMetadata(&api.PrefixMetadata{ 299 Prefix: "abc", 300 Acls: []*api.PrefixMetadata_ACL{ 301 {Role: api.Role_READER}, 302 {Role: api.Role_READER}, 303 }, 304 }), ShouldErrLike, "role READER is specified twice") 305 }) 306 307 Convey("Bad principal", t, func() { 308 So(NormalizePrefixMetadata(&api.PrefixMetadata{ 309 Prefix: "abc", 310 Acls: []*api.PrefixMetadata_ACL{ 311 {Role: api.Role_READER, Principals: []string{":"}}, 312 }, 313 }), ShouldErrLike, `in ACL entry for role READER: ":" doesn't look like a principal id`) 314 }) 315 } 316 317 func TestPinToString(t *testing.T) { 318 t.Parallel() 319 320 Convey("Pin.String works", t, func() { 321 So( 322 fmt.Sprintf("%s", Pin{"good/name", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}), 323 ShouldEqual, 324 "good/name:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") 325 }) 326 } 327 328 func TestPinSliceAndMap(t *testing.T) { 329 t.Parallel() 330 331 Convey("PinSlice", t, func() { 332 ps := PinSlice{{"pkg2", "vers"}, {"pkg", "vers"}} 333 334 Convey("can convert to a map", func() { 335 pm := ps.ToMap() 336 So(pm, ShouldResemble, PinMap{ 337 "pkg": "vers", 338 "pkg2": "vers", 339 }) 340 341 pm["new/pkg"] = "some:tag" 342 343 Convey("and back to a slice", func() { 344 So(pm.ToSlice(), ShouldResemble, PinSlice{ 345 {"new/pkg", "some:tag"}, 346 {"pkg", "vers"}, 347 {"pkg2", "vers"}, 348 }) 349 }) 350 }) 351 }) 352 353 Convey("PinSliceBySubdir", t, func() { 354 id := func(letter rune) string { 355 return strings.Repeat(string(letter), 40) 356 } 357 358 pmr := PinSliceBySubdir{ 359 "": PinSlice{ 360 {"pkg2", id('1')}, 361 {"pkg", id('0')}, 362 }, 363 "other": PinSlice{ 364 {"something", id('2')}, 365 }, 366 } 367 368 Convey("Can validate", func() { 369 So(pmr.Validate(AnyHash), ShouldErrLike, nil) 370 371 Convey("can see bad subdirs", func() { 372 pmr["/"] = PinSlice{{"something", "version"}} 373 So(pmr.Validate(AnyHash), ShouldErrLike, "bad subdir") 374 }) 375 376 Convey("can see duplicate packages", func() { 377 pmr[""] = append(pmr[""], Pin{"pkg", strings.Repeat("2", 40)}) 378 So(pmr.Validate(AnyHash), ShouldErrLike, `subdir "": duplicate package "pkg"`) 379 }) 380 381 Convey("can see bad pins", func() { 382 pmr[""] = append(pmr[""], Pin{"quxxly", "nurbs"}) 383 So(pmr.Validate(AnyHash), ShouldErrLike, `subdir "": not a valid package instance ID`) 384 }) 385 }) 386 387 Convey("can convert to ByMap", func() { 388 pmm := pmr.ToMap() 389 So(pmm, ShouldResemble, PinMapBySubdir{ 390 "": PinMap{ 391 "pkg": id('0'), 392 "pkg2": id('1'), 393 }, 394 "other": PinMap{ 395 "something": id('2'), 396 }, 397 }) 398 399 Convey("and back", func() { 400 So(pmm.ToSlice(), ShouldResemble, PinSliceBySubdir{ 401 "": PinSlice{ 402 {"pkg", id('0')}, 403 {"pkg2", id('1')}, 404 }, 405 "other": PinSlice{ 406 {"something", id('2')}, 407 }, 408 }) 409 }) 410 }) 411 412 }) 413 } 414 415 func TestInstanceMetadata(t *testing.T) { 416 t.Parallel() 417 418 Convey("ValidateInstanceMetadataKey works", t, func() { 419 So(ValidateInstanceMetadataKey("a"), ShouldBeNil) 420 So(ValidateInstanceMetadataKey("az_-09"), ShouldBeNil) 421 So(ValidateInstanceMetadataKey(strings.Repeat("z", 400)), ShouldBeNil) 422 423 So(ValidateInstanceMetadataKey(""), ShouldNotBeNil) 424 So(ValidateInstanceMetadataKey(strings.Repeat("z", 401)), ShouldNotBeNil) 425 So(ValidateInstanceMetadataKey("a a"), ShouldNotBeNil) 426 So(ValidateInstanceMetadataKey("A"), ShouldNotBeNil) 427 So(ValidateInstanceMetadataKey("a:a"), ShouldNotBeNil) 428 }) 429 430 Convey("ValidateContentType works", t, func() { 431 So(ValidateContentType(""), ShouldBeNil) 432 So(ValidateContentType("text/plain; encoding=utf-8"), ShouldBeNil) 433 434 So(ValidateContentType("zzz zzz"), ShouldNotBeNil) 435 So(ValidateContentType(strings.Repeat("z", 401)), ShouldNotBeNil) 436 }) 437 438 Convey("InstanceMetadataFingerprint works", t, func() { 439 fp := InstanceMetadataFingerprint("key", []byte("value")) 440 So(fp, ShouldEqual, "06dd3884aa86b22603764bd7e5b0b41e") 441 }) 442 443 Convey("ValidateInstanceMetadataFingerprint works", t, func() { 444 fp := InstanceMetadataFingerprint("key", []byte("value")) 445 So(ValidateInstanceMetadataFingerprint(fp), ShouldBeNil) 446 So(ValidateInstanceMetadataFingerprint("aaaa"), ShouldNotBeNil) 447 So(ValidateInstanceMetadataFingerprint(strings.Repeat("Z", 32)), ShouldNotBeNil) 448 }) 449 }