| // Copyright 2014 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package common |
| |
| import ( |
| "fmt" |
| "strings" |
| "testing" |
| |
| api "go.chromium.org/luci/cipd/api/cipd/v1" |
| |
| . "github.com/smartystreets/goconvey/convey" |
| . "go.chromium.org/luci/common/testing/assertions" |
| ) |
| |
| func TestValidatePackageName(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidatePackageName works", t, func() { |
| So(ValidatePackageName("good/name"), ShouldBeNil) |
| So(ValidatePackageName("good_name"), ShouldBeNil) |
| So(ValidatePackageName("123-_/also/good/name"), ShouldBeNil) |
| So(ValidatePackageName("good.name/.name/..name"), ShouldBeNil) |
| So(ValidatePackageName(""), ShouldNotBeNil) |
| So(ValidatePackageName("/"), ShouldNotBeNil) |
| So(ValidatePackageName("BAD/name"), ShouldNotBeNil) |
| So(ValidatePackageName("bad//name"), ShouldNotBeNil) |
| So(ValidatePackageName("bad/name/"), ShouldNotBeNil) |
| So(ValidatePackageName("/bad/name"), ShouldNotBeNil) |
| So(ValidatePackageName("bad/name\nyeah"), ShouldNotBeNil) |
| So(ValidatePackageName("./name"), ShouldNotBeNil) |
| So(ValidatePackageName("name/../name"), ShouldNotBeNil) |
| So(ValidatePackageName("../../yeah"), ShouldNotBeNil) |
| So(ValidatePackageName("..."), ShouldNotBeNil) |
| }) |
| } |
| |
| func TestValidatePackagePrefix(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidatePackagePrefix strips suffix", t, func() { |
| p, err := ValidatePackagePrefix("good/name/") |
| So(err, ShouldBeNil) |
| So(p, ShouldEqual, "good/name") |
| }) |
| |
| Convey("ValidatePackagePrefix works", t, func() { |
| call := func(p string) error { |
| _, err := ValidatePackagePrefix(p) |
| return err |
| } |
| |
| So(call("good/name"), ShouldBeNil) |
| So(call("good/name/"), ShouldBeNil) |
| So(call("good_name"), ShouldBeNil) |
| So(call("123-_/also/good/name"), ShouldBeNil) |
| So(call("good.name/.name/..name"), ShouldBeNil) |
| So(call(""), ShouldBeNil) // repo root |
| So(call("/"), ShouldBeNil) // repo root |
| So(call("BAD/name"), ShouldNotBeNil) |
| So(call("bad//name"), ShouldNotBeNil) |
| So(call("bad/name//"), ShouldNotBeNil) |
| So(call("/bad/name"), ShouldNotBeNil) |
| So(call("bad/name\nyeah"), ShouldNotBeNil) |
| So(call("./name"), ShouldNotBeNil) |
| So(call("name/../name"), ShouldNotBeNil) |
| So(call("../../yeah"), ShouldNotBeNil) |
| So(call("..."), ShouldNotBeNil) |
| }) |
| } |
| |
| func TestValidateInstanceTag(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidateInstanceTag works", t, func() { |
| So(ValidateInstanceTag(""), ShouldNotBeNil) |
| So(ValidateInstanceTag("notapair"), ShouldNotBeNil) |
| So(ValidateInstanceTag(strings.Repeat("long", 200)+":abc"), ShouldNotBeNil) |
| So(ValidateInstanceTag("BADKEY:value"), ShouldNotBeNil) |
| So(ValidateInstanceTag("empty_val:"), ShouldNotBeNil) |
| So(ValidateInstanceTag(" space:a"), ShouldNotBeNil) |
| So(ValidateInstanceTag("space :a"), ShouldNotBeNil) |
| So(ValidateInstanceTag("space: a"), ShouldNotBeNil) |
| So(ValidateInstanceTag("space:a "), ShouldNotBeNil) |
| So(ValidateInstanceTag("newline:a\n"), ShouldNotBeNil) |
| So(ValidateInstanceTag("tab:a\tb"), ShouldNotBeNil) |
| So(ValidateInstanceTag("good:tag"), ShouldBeNil) |
| So(ValidateInstanceTag("good:tag:blah"), ShouldBeNil) |
| So(ValidateInstanceTag("good_tag:A a0$()*+,-./:;<=>@\\_{}~"), ShouldBeNil) |
| }) |
| } |
| |
| func TestParseInstanceTag(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ParseInstanceTag works", t, func() { |
| t, err := ParseInstanceTag("good:tag") |
| So(err, ShouldBeNil) |
| So(t, ShouldResembleProto, &api.Tag{ |
| Key: "good", |
| Value: "tag", |
| }) |
| |
| t, err = ParseInstanceTag("good:tag:blah") |
| So(err, ShouldBeNil) |
| So(t, ShouldResembleProto, &api.Tag{ |
| Key: "good", |
| Value: "tag:blah", |
| }) |
| |
| t, err = ParseInstanceTag("good_tag:A a0$()*+,-./:;<=>@\\_{}~") |
| So(err, ShouldBeNil) |
| So(t, ShouldResembleProto, &api.Tag{ |
| Key: "good_tag", |
| Value: "A a0$()*+,-./:;<=>@\\_{}~", |
| }) |
| |
| t, err = ParseInstanceTag("") |
| So(err, ShouldNotBeNil) |
| |
| t, err = ParseInstanceTag("notapair") |
| So(err, ShouldNotBeNil) |
| |
| t, err = ParseInstanceTag(strings.Repeat("long", 200) + ":abc") |
| So(err, ShouldNotBeNil) |
| |
| t, err = ParseInstanceTag("BADKEY:value") |
| So(err, ShouldNotBeNil) |
| }) |
| |
| Convey("MustParseInstanceTag panics on bad tag", t, func() { |
| So(func() { MustParseInstanceTag("") }, ShouldPanic) |
| }) |
| } |
| |
| func TestValidatePin(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidatePin works", t, func() { |
| So(ValidatePin(Pin{"good/name", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, KnownHash), ShouldBeNil) |
| So(ValidatePin(Pin{"BAD/name", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, KnownHash), ShouldNotBeNil) |
| So(ValidatePin(Pin{"good/name", "aaaaaaaaaaa"}, KnownHash), ShouldNotBeNil) |
| }) |
| } |
| |
| func TestValidatePackageRef(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidatePackageRef works", t, func() { |
| So(ValidatePackageRef("some-ref"), ShouldBeNil) |
| So(ValidatePackageRef("ref/with/slashes.and.dots"), ShouldBeNil) |
| |
| So(ValidatePackageRef(""), ShouldNotBeNil) |
| So(ValidatePackageRef("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), ShouldNotBeNil) |
| So(ValidatePackageRef("good:tag"), ShouldNotBeNil) |
| }) |
| } |
| |
| func TestValidateInstanceVersion(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidateInstanceVersion works", t, func() { |
| So(ValidateInstanceVersion("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), ShouldBeNil) |
| So(ValidateInstanceVersion("good:tag"), ShouldBeNil) |
| So(ValidatePackageRef("some-read"), ShouldBeNil) |
| So(ValidateInstanceVersion("BADTAG:"), ShouldNotBeNil) |
| }) |
| } |
| |
| func TestValidateSubdir(t *testing.T) { |
| t.Parallel() |
| |
| badSubdirs := []struct { |
| name string |
| subdir string |
| err string |
| }{ |
| {"windows", "folder\\thing", "backslashes are not allowed"}, |
| {"windows drive", "c:/foo/bar", `colons are not allowed`}, |
| {"messy", "some/../thing", `"some/../thing": should be simplified to "thing"`}, |
| {"relative", "../something", `contains disallowed dot-path prefix`}, |
| {"single relative", "./something", `"./something": should be simplified to "something"`}, |
| {"absolute", "/etc", `absolute paths are not allowed`}, |
| {"extra slashes", "//foo/bar", `bad subdir`}, |
| } |
| |
| goodSubdirs := []struct { |
| name string |
| subdir string |
| }{ |
| {"empty", ""}, |
| {"simple path", "some/path"}, |
| {"single path", "something"}, |
| {"spaces", "some path/with/ spaces"}, |
| } |
| |
| Convey("ValidtateSubdir", t, func() { |
| Convey("rejects bad subdirs", func() { |
| for _, tc := range badSubdirs { |
| Convey(tc.name, func() { |
| So(ValidateSubdir(tc.subdir), ShouldErrLike, tc.err) |
| }) |
| } |
| }) |
| |
| Convey("accepts good subdirs", func() { |
| for _, tc := range goodSubdirs { |
| Convey(tc.name, func() { |
| So(ValidateSubdir(tc.subdir), ShouldErrLike, nil) |
| }) |
| } |
| }) |
| }) |
| } |
| |
| func TestValidatePrincipalName(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidatePrincipalName OK", t, func() { |
| cases := []string{ |
| "group:abc", |
| "user:a@example.com", |
| "anonymous:anonymous", |
| "bot:blah", |
| "service:blah", |
| } |
| for _, tc := range cases { |
| So(ValidatePrincipalName(tc), ShouldBeNil) |
| } |
| }) |
| |
| Convey("ValidatePrincipalName not OK", t, func() { |
| cases := []struct{ p, err string }{ |
| {"", "doesn't look like a principal id"}, |
| {":", "doesn't look like a principal id"}, |
| {":zzz", "doesn't look like a principal id"}, |
| {"group:", "doesn't look like a principal id"}, |
| {"user:", "doesn't look like a principal id"}, |
| {"anonymous:zzz", `bad value "zzz" for identity kind "anonymous"`}, |
| {"user:abc", `bad value "abc" for identity kind "user"`}, |
| } |
| for _, tc := range cases { |
| So(ValidatePrincipalName(tc.p), ShouldErrLike, tc.err) |
| } |
| }) |
| } |
| |
| func TestNormalizePrefixMetadata(t *testing.T) { |
| t.Parallel() |
| |
| Convey("Happy path", t, func() { |
| m := &api.PrefixMetadata{ |
| Prefix: "abc/", |
| Acls: []*api.PrefixMetadata_ACL{ |
| {Role: api.Role_OWNER, Principals: []string{"user:abc@example.com", "group:a"}}, |
| {Role: api.Role_READER, Principals: []string{"group:z"}}, |
| {Role: 123}, // some future unknown role |
| }, |
| } |
| So(NormalizePrefixMetadata(m), ShouldBeNil) |
| So(m, ShouldResembleProto, &api.PrefixMetadata{ |
| Prefix: "abc", |
| Acls: []*api.PrefixMetadata_ACL{ |
| {Role: api.Role_READER, Principals: []string{"group:z"}}, |
| {Role: api.Role_OWNER, Principals: []string{"group:a", "user:abc@example.com"}}, |
| {Role: 123}, |
| }, |
| }) |
| }) |
| |
| Convey("Validates prefix", t, func() { |
| So(NormalizePrefixMetadata(&api.PrefixMetadata{Prefix: "//"}), |
| ShouldErrLike, "invalid package prefix") |
| }) |
| |
| Convey("No role", t, func() { |
| So(NormalizePrefixMetadata(&api.PrefixMetadata{ |
| Prefix: "abc", |
| Acls: []*api.PrefixMetadata_ACL{ |
| {}, |
| }, |
| }), ShouldErrLike, "ACL entry #0 doesn't have a role specified") |
| }) |
| |
| Convey("Double ACL entries", t, func() { |
| So(NormalizePrefixMetadata(&api.PrefixMetadata{ |
| Prefix: "abc", |
| Acls: []*api.PrefixMetadata_ACL{ |
| {Role: api.Role_READER}, |
| {Role: api.Role_READER}, |
| }, |
| }), ShouldErrLike, "role READER is specified twice") |
| }) |
| |
| Convey("Bad principal", t, func() { |
| So(NormalizePrefixMetadata(&api.PrefixMetadata{ |
| Prefix: "abc", |
| Acls: []*api.PrefixMetadata_ACL{ |
| {Role: api.Role_READER, Principals: []string{":"}}, |
| }, |
| }), ShouldErrLike, `in ACL entry for role READER: ":" doesn't look like a principal id`) |
| }) |
| } |
| |
| func TestPinToString(t *testing.T) { |
| t.Parallel() |
| |
| Convey("Pin.String works", t, func() { |
| So( |
| fmt.Sprintf("%s", Pin{"good/name", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}), |
| ShouldEqual, |
| "good/name:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") |
| }) |
| } |
| |
| func TestPinSliceAndMap(t *testing.T) { |
| t.Parallel() |
| |
| Convey("PinSlice", t, func() { |
| ps := PinSlice{{"pkg2", "vers"}, {"pkg", "vers"}} |
| |
| Convey("can convert to a map", func() { |
| pm := ps.ToMap() |
| So(pm, ShouldResemble, PinMap{ |
| "pkg": "vers", |
| "pkg2": "vers", |
| }) |
| |
| pm["new/pkg"] = "some:tag" |
| |
| Convey("and back to a slice", func() { |
| So(pm.ToSlice(), ShouldResemble, PinSlice{ |
| {"new/pkg", "some:tag"}, |
| {"pkg", "vers"}, |
| {"pkg2", "vers"}, |
| }) |
| }) |
| }) |
| }) |
| |
| Convey("PinSliceBySubdir", t, func() { |
| id := func(letter rune) string { |
| return strings.Repeat(string(letter), 40) |
| } |
| |
| pmr := PinSliceBySubdir{ |
| "": PinSlice{ |
| {"pkg2", id('1')}, |
| {"pkg", id('0')}, |
| }, |
| "other": PinSlice{ |
| {"something", id('2')}, |
| }, |
| } |
| |
| Convey("Can validate", func() { |
| So(pmr.Validate(AnyHash), ShouldErrLike, nil) |
| |
| Convey("can see bad subdirs", func() { |
| pmr["/"] = PinSlice{{"something", "version"}} |
| So(pmr.Validate(AnyHash), ShouldErrLike, "bad subdir") |
| }) |
| |
| Convey("can see duplicate packages", func() { |
| pmr[""] = append(pmr[""], Pin{"pkg", strings.Repeat("2", 40)}) |
| So(pmr.Validate(AnyHash), ShouldErrLike, `subdir "": duplicate package "pkg"`) |
| }) |
| |
| Convey("can see bad pins", func() { |
| pmr[""] = append(pmr[""], Pin{"quxxly", "nurbs"}) |
| So(pmr.Validate(AnyHash), ShouldErrLike, `subdir "": not a valid package instance ID`) |
| }) |
| }) |
| |
| Convey("can convert to ByMap", func() { |
| pmm := pmr.ToMap() |
| So(pmm, ShouldResemble, PinMapBySubdir{ |
| "": PinMap{ |
| "pkg": id('0'), |
| "pkg2": id('1'), |
| }, |
| "other": PinMap{ |
| "something": id('2'), |
| }, |
| }) |
| |
| Convey("and back", func() { |
| So(pmm.ToSlice(), ShouldResemble, PinSliceBySubdir{ |
| "": PinSlice{ |
| {"pkg", id('0')}, |
| {"pkg2", id('1')}, |
| }, |
| "other": PinSlice{ |
| {"something", id('2')}, |
| }, |
| }) |
| }) |
| }) |
| |
| }) |
| } |
| |
| func TestInstanceMetadata(t *testing.T) { |
| t.Parallel() |
| |
| Convey("ValidateInstanceMetadataKey works", t, func() { |
| So(ValidateInstanceMetadataKey("a"), ShouldBeNil) |
| So(ValidateInstanceMetadataKey("az_-09"), ShouldBeNil) |
| So(ValidateInstanceMetadataKey(strings.Repeat("z", 400)), ShouldBeNil) |
| |
| So(ValidateInstanceMetadataKey(""), ShouldNotBeNil) |
| So(ValidateInstanceMetadataKey(strings.Repeat("z", 401)), ShouldNotBeNil) |
| So(ValidateInstanceMetadataKey("a a"), ShouldNotBeNil) |
| So(ValidateInstanceMetadataKey("A"), ShouldNotBeNil) |
| So(ValidateInstanceMetadataKey("a:a"), ShouldNotBeNil) |
| }) |
| |
| Convey("ValidateContentType works", t, func() { |
| So(ValidateContentType(""), ShouldBeNil) |
| So(ValidateContentType("text/plain; encoding=utf-8"), ShouldBeNil) |
| |
| So(ValidateContentType("zzz zzz"), ShouldNotBeNil) |
| So(ValidateContentType(strings.Repeat("z", 401)), ShouldNotBeNil) |
| }) |
| |
| Convey("InstanceMetadataFingerprint works", t, func() { |
| fp := InstanceMetadataFingerprint("key", []byte("value")) |
| So(fp, ShouldEqual, "06dd3884aa86b22603764bd7e5b0b41e") |
| }) |
| |
| Convey("ValidateInstanceMetadataFingerprint works", t, func() { |
| fp := InstanceMetadataFingerprint("key", []byte("value")) |
| So(ValidateInstanceMetadataFingerprint(fp), ShouldBeNil) |
| So(ValidateInstanceMetadataFingerprint("aaaa"), ShouldNotBeNil) |
| So(ValidateInstanceMetadataFingerprint(strings.Repeat("Z", 32)), ShouldNotBeNil) |
| }) |
| } |