github.com/juju/charm/v11@v11.2.0/url_test.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm_test 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "regexp" 10 "strings" 11 12 "github.com/juju/mgo/v3/bson" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/yaml.v2" 15 16 "github.com/juju/charm/v11" 17 ) 18 19 type URLSuite struct{} 20 21 var _ = gc.Suite(&URLSuite{}) 22 23 var urlTests = []struct { 24 s, err string 25 exact string 26 url *charm.URL 27 }{{ 28 s: "local:series/name-1", 29 url: &charm.URL{"local", "name", 1, "series", ""}, 30 }, { 31 s: "local:series/name", 32 url: &charm.URL{"local", "name", -1, "series", ""}, 33 }, { 34 s: "local:series/n0-0n-n0", 35 url: &charm.URL{"local", "n0-0n-n0", -1, "series", ""}, 36 }, { 37 s: "local:name", 38 url: &charm.URL{"local", "name", -1, "", ""}, 39 }, { 40 s: "bs:~user/series/name-1", 41 err: `cannot parse URL $URL: schema "bs" not valid`, 42 }, { 43 s: ":foo", 44 err: `cannot parse charm or bundle URL: $URL`, 45 }, { 46 s: "local:~user/series/name", 47 err: `local charm or bundle URL with user name: $URL`, 48 }, { 49 s: "local:~user/name", 50 err: `local charm or bundle URL with user name: $URL`, 51 }, { 52 s: "amd64/name", 53 url: &charm.URL{"ch", "name", -1, "", "amd64"}, 54 exact: "ch:amd64/name", 55 }, { 56 s: "foo", 57 url: &charm.URL{"ch", "foo", -1, "", ""}, 58 exact: "ch:foo", 59 }, { 60 s: "foo-1", 61 exact: "ch:foo-1", 62 url: &charm.URL{"ch", "foo", 1, "", ""}, 63 }, { 64 s: "n0-n0-n0", 65 exact: "ch:n0-n0-n0", 66 url: &charm.URL{"ch", "n0-n0-n0", -1, "", ""}, 67 }, { 68 s: "local:foo", 69 exact: "local:foo", 70 url: &charm.URL{"local", "foo", -1, "", ""}, 71 }, { 72 s: "arm64/series/bar", 73 url: &charm.URL{"ch", "bar", -1, "series", "arm64"}, 74 exact: "ch:arm64/series/bar", 75 }, { 76 s: "ch:name", 77 url: &charm.URL{"ch", "name", -1, "", ""}, 78 }, { 79 s: "ch:name-suffix", 80 url: &charm.URL{"ch", "name-suffix", -1, "", ""}, 81 }, { 82 s: "ch:name-1", 83 url: &charm.URL{"ch", "name", 1, "", ""}, 84 }, { 85 s: "ch:focal/istio-gateway-74", 86 url: &charm.URL{"ch", "istio-gateway", 74, "focal", ""}, 87 }, { 88 s: "ch:amd64/istio-gateway-74", 89 url: &charm.URL{"ch", "istio-gateway", 74, "", "amd64"}, 90 }, { 91 s: "ch:arm64/name", 92 url: &charm.URL{"ch", "name", -1, "", "arm64"}, 93 exact: "ch:arm64/name", 94 }, { 95 s: "ch:~user/name", 96 err: `charmhub charm or bundle URL with user name: "ch:~user/name" not valid`, 97 }, { 98 s: "ch:purple/series/name-0", 99 err: `in URL "ch:purple/series/name-0": architecture name "purple" not valid`, 100 }, { 101 s: "ch:nam-!e", 102 err: `cannot parse name and/or revision in URL "ch:nam-!e": name "nam-!e" not valid`, 103 }, { 104 s: "cs:testme", 105 err: `cannot parse URL "cs:testme": schema "cs" not valid`, 106 }} 107 108 func (s *URLSuite) TestParseURL(c *gc.C) { 109 for i, t := range urlTests { 110 c.Logf("test %d: %q", i, t.s) 111 112 expectStr := t.s 113 if t.exact != "" { 114 expectStr = t.exact 115 } 116 url, uerr := charm.ParseURL(t.s) 117 if t.err != "" { 118 t.err = strings.Replace(t.err, "$URL", regexp.QuoteMeta(fmt.Sprintf("%q", t.s)), -1) 119 c.Check(uerr, gc.ErrorMatches, t.err) 120 c.Check(url, gc.IsNil) 121 continue 122 } 123 c.Assert(uerr, gc.IsNil) 124 c.Check(url, gc.DeepEquals, t.url) 125 c.Check(url.String(), gc.Equals, expectStr) 126 127 // URL strings are generated as expected. Reversability is preserved 128 // with v1 URLs. 129 if t.exact != "" { 130 c.Check(url.String(), gc.Equals, t.exact) 131 } else { 132 c.Check(url.String(), gc.Equals, t.s) 133 } 134 } 135 } 136 137 var ensureSchemaTests = []struct { 138 input, expected, err string 139 }{ 140 {input: "foo", expected: "ch:foo"}, 141 {input: "foo-1", expected: "ch:foo-1"}, 142 {input: "~user/foo", expected: "ch:~user/foo"}, 143 {input: "series/foo", expected: "ch:series/foo"}, 144 {input: "local:foo", expected: "local:foo"}, 145 { 146 input: "unknown:foo", 147 err: `schema "unknown" not valid`, 148 }, 149 } 150 151 func (s *URLSuite) TestInferURLNoDefaultSeries(c *gc.C) { 152 for i, t := range ensureSchemaTests { 153 c.Logf("%d: %s", i, t.input) 154 inferred, err := charm.EnsureSchema(t.input, charm.CharmHub) 155 if t.err != "" { 156 c.Assert(err, gc.ErrorMatches, t.err) 157 continue 158 } 159 160 c.Assert(err, gc.IsNil) 161 c.Assert(inferred, gc.Equals, t.expected) 162 } 163 } 164 165 var validTests = []struct { 166 valid func(string) bool 167 string string 168 expect bool 169 }{ 170 171 {charm.IsValidName, "", false}, 172 {charm.IsValidName, "wordpress", true}, 173 {charm.IsValidName, "Wordpress", false}, 174 {charm.IsValidName, "word-press", true}, 175 {charm.IsValidName, "word press", false}, 176 {charm.IsValidName, "word^press", false}, 177 {charm.IsValidName, "-wordpress", false}, 178 {charm.IsValidName, "wordpress-", false}, 179 {charm.IsValidName, "wordpress2", true}, 180 {charm.IsValidName, "wordpress-2", false}, 181 {charm.IsValidName, "word2-press2", true}, 182 183 {charm.IsValidSeries, "", false}, 184 {charm.IsValidSeries, "precise", true}, 185 {charm.IsValidSeries, "Precise", false}, 186 {charm.IsValidSeries, "pre cise", false}, 187 {charm.IsValidSeries, "pre-cise", false}, 188 {charm.IsValidSeries, "pre^cise", false}, 189 {charm.IsValidSeries, "prec1se", true}, 190 {charm.IsValidSeries, "-precise", false}, 191 {charm.IsValidSeries, "precise-", false}, 192 {charm.IsValidSeries, "precise-1", false}, 193 {charm.IsValidSeries, "precise1", true}, 194 {charm.IsValidSeries, "pre-c1se", false}, 195 196 {charm.IsValidArchitecture, "amd64", true}, 197 {charm.IsValidArchitecture, "~amd64", false}, 198 {charm.IsValidArchitecture, "not-an-arch", false}, 199 } 200 201 func (s *URLSuite) TestValidCheckers(c *gc.C) { 202 for i, t := range validTests { 203 c.Logf("test %d: %s", i, t.string) 204 c.Assert(t.valid(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string)) 205 } 206 } 207 208 func (s *URLSuite) TestMustParseURL(c *gc.C) { 209 url := charm.MustParseURL("ch:series/name") 210 c.Assert(url, gc.DeepEquals, &charm.URL{"ch", "name", -1, "series", ""}) 211 f := func() { charm.MustParseURL("local:@@/name") } 212 c.Assert(f, gc.PanicMatches, "cannot parse URL \"local:@@/name\": series name \"@@\" not valid") 213 } 214 215 func (s *URLSuite) TestWithRevision(c *gc.C) { 216 url := charm.MustParseURL("ch:series/name") 217 other := url.WithRevision(1) 218 c.Assert(url, gc.DeepEquals, &charm.URL{"ch", "name", -1, "series", ""}) 219 c.Assert(other, gc.DeepEquals, &charm.URL{"ch", "name", 1, "series", ""}) 220 221 // Should always copy. The opposite behavior is error prone. 222 c.Assert(other.WithRevision(1), gc.Not(gc.Equals), other) 223 c.Assert(other.WithRevision(1), gc.DeepEquals, other) 224 } 225 226 var codecs = []struct { 227 Name string 228 Marshal func(interface{}) ([]byte, error) 229 Unmarshal func([]byte, interface{}) error 230 }{{ 231 Name: "bson", 232 Marshal: bson.Marshal, 233 Unmarshal: bson.Unmarshal, 234 }, { 235 Name: "json", 236 Marshal: json.Marshal, 237 Unmarshal: json.Unmarshal, 238 }, { 239 Name: "yaml", 240 Marshal: yaml.Marshal, 241 Unmarshal: yaml.Unmarshal, 242 }} 243 244 func (s *URLSuite) TestURLCodecs(c *gc.C) { 245 for i, codec := range codecs { 246 c.Logf("codec %d: %v", i, codec.Name) 247 type doc struct { 248 URL *charm.URL `json:",omitempty" bson:",omitempty" yaml:",omitempty"` 249 } 250 url := charm.MustParseURL("ch:name") 251 v0 := doc{url} 252 data, err := codec.Marshal(v0) 253 c.Assert(err, gc.IsNil) 254 var v doc 255 err = codec.Unmarshal(data, &v) 256 c.Assert(v, gc.DeepEquals, v0) 257 258 // Check that the underlying representation 259 // is a string. 260 type strDoc struct { 261 URL string 262 } 263 var vs strDoc 264 err = codec.Unmarshal(data, &vs) 265 c.Assert(err, gc.IsNil) 266 c.Assert(vs.URL, gc.Equals, "ch:name") 267 268 data, err = codec.Marshal(doc{}) 269 c.Assert(err, gc.IsNil) 270 v = doc{} 271 err = codec.Unmarshal(data, &v) 272 c.Assert(err, gc.IsNil) 273 c.Assert(v.URL, gc.IsNil, gc.Commentf("data: %q", data)) 274 } 275 } 276 277 func (s *URLSuite) TestJSONGarbage(c *gc.C) { 278 // unmarshalling json gibberish 279 for _, value := range []string{":{", `"ch:{}+<"`, `"ch:~_~/f00^^&^/baaaar$%-?"`} { 280 err := json.Unmarshal([]byte(value), new(struct{ URL *charm.URL })) 281 c.Check(err, gc.NotNil) 282 } 283 } 284 285 type QuoteSuite struct{} 286 287 var _ = gc.Suite(&QuoteSuite{}) 288 289 func (s *QuoteSuite) TestUnmodified(c *gc.C) { 290 // Check that a string containing only valid 291 // chars stays unmodified. 292 in := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-" 293 out := charm.Quote(in) 294 c.Assert(out, gc.Equals, in) 295 } 296 297 func (s *QuoteSuite) TestQuote(c *gc.C) { 298 // Check that invalid chars are translated correctly. 299 in := "hello_there/how'are~you-today.sir" 300 out := charm.Quote(in) 301 c.Assert(out, gc.Equals, "hello_5f_there_2f_how_27_are_7e_you-today.sir") 302 }