gopkg.in/juju/charm.v6-unstable@v6.0.0-20171026192109-50d0c219b496/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 gc "gopkg.in/check.v1" 13 "gopkg.in/mgo.v2/bson" 14 "gopkg.in/yaml.v2" 15 16 "gopkg.in/juju/charm.v6-unstable" 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: "cs:~user/series/name", 29 url: &charm.URL{"cs", "user", "name", -1, "series"}, 30 }, { 31 s: "cs:~user/series/name-0", 32 url: &charm.URL{"cs", "user", "name", 0, "series"}, 33 }, { 34 s: "cs:series/name", 35 url: &charm.URL{"cs", "", "name", -1, "series"}, 36 }, { 37 s: "cs:series/name-42", 38 url: &charm.URL{"cs", "", "name", 42, "series"}, 39 }, { 40 s: "local:series/name-1", 41 url: &charm.URL{"local", "", "name", 1, "series"}, 42 }, { 43 s: "local:series/name", 44 url: &charm.URL{"local", "", "name", -1, "series"}, 45 }, { 46 s: "local:series/n0-0n-n0", 47 url: &charm.URL{"local", "", "n0-0n-n0", -1, "series"}, 48 }, { 49 s: "cs:~user/name", 50 url: &charm.URL{"cs", "user", "name", -1, ""}, 51 }, { 52 s: "cs:name", 53 url: &charm.URL{"cs", "", "name", -1, ""}, 54 }, { 55 s: "local:name", 56 url: &charm.URL{"local", "", "name", -1, ""}, 57 }, { 58 s: "http://jujucharms.com/u/user/name/series/1", 59 url: &charm.URL{"cs", "user", "name", 1, "series"}, 60 exact: "cs:~user/series/name-1", 61 }, { 62 s: "http://www.jujucharms.com/u/user/name/series/1", 63 url: &charm.URL{"cs", "user", "name", 1, "series"}, 64 exact: "cs:~user/series/name-1", 65 }, { 66 s: "https://www.jujucharms.com/u/user/name/series/1", 67 url: &charm.URL{"cs", "user", "name", 1, "series"}, 68 exact: "cs:~user/series/name-1", 69 }, { 70 s: "https://jujucharms.com/u/user/name/series/1", 71 url: &charm.URL{"cs", "user", "name", 1, "series"}, 72 exact: "cs:~user/series/name-1", 73 }, { 74 s: "https://jujucharms.com/u/user/name/series", 75 url: &charm.URL{"cs", "user", "name", -1, "series"}, 76 exact: "cs:~user/series/name", 77 }, { 78 s: "https://jujucharms.com/u/user/name/1", 79 url: &charm.URL{"cs", "user", "name", 1, ""}, 80 exact: "cs:~user/name-1", 81 }, { 82 s: "https://jujucharms.com/u/user/name", 83 url: &charm.URL{"cs", "user", "name", -1, ""}, 84 exact: "cs:~user/name", 85 }, { 86 s: "https://jujucharms.com/name", 87 url: &charm.URL{"cs", "", "name", -1, ""}, 88 exact: "cs:name", 89 }, { 90 s: "https://jujucharms.com/name/series", 91 url: &charm.URL{"cs", "", "name", -1, "series"}, 92 exact: "cs:series/name", 93 }, { 94 s: "https://jujucharms.com/name/1", 95 url: &charm.URL{"cs", "", "name", 1, ""}, 96 exact: "cs:name-1", 97 }, { 98 s: "https://jujucharms.com/name/series/1", 99 url: &charm.URL{"cs", "", "name", 1, "series"}, 100 exact: "cs:series/name-1", 101 }, { 102 s: "https://jujucharms.com/u/user/name/series/1/", 103 url: &charm.URL{"cs", "user", "name", 1, "series"}, 104 exact: "cs:~user/series/name-1", 105 }, { 106 s: "https://jujucharms.com/u/user/name/series/", 107 url: &charm.URL{"cs", "user", "name", -1, "series"}, 108 exact: "cs:~user/series/name", 109 }, { 110 s: "https://jujucharms.com/u/user/name/1/", 111 url: &charm.URL{"cs", "user", "name", 1, ""}, 112 exact: "cs:~user/name-1", 113 }, { 114 s: "https://jujucharms.com/u/user/name/", 115 url: &charm.URL{"cs", "user", "name", -1, ""}, 116 exact: "cs:~user/name", 117 }, { 118 s: "https://jujucharms.com/name/", 119 url: &charm.URL{"cs", "", "name", -1, ""}, 120 exact: "cs:name", 121 }, { 122 s: "https://jujucharms.com/name/series/", 123 url: &charm.URL{"cs", "", "name", -1, "series"}, 124 exact: "cs:series/name", 125 }, { 126 s: "https://jujucharms.com/name/1/", 127 url: &charm.URL{"cs", "", "name", 1, ""}, 128 exact: "cs:name-1", 129 }, { 130 s: "https://jujucharms.com/name/series/1/", 131 url: &charm.URL{"cs", "", "name", 1, "series"}, 132 exact: "cs:series/name-1", 133 }, { 134 s: "https://jujucharms.com/", 135 err: `cannot parse URL $URL: name "" not valid`, 136 }, { 137 s: "https://jujucharms.com/bad.wolf", 138 err: `cannot parse URL $URL: name "bad.wolf" not valid`, 139 }, { 140 s: "https://jujucharms.com/u/", 141 err: "charm or bundle URL $URL malformed, expected \"/u/<user>/<name>\"", 142 }, { 143 s: "https://jujucharms.com/u/badwolf", 144 err: "charm or bundle URL $URL malformed, expected \"/u/<user>/<name>\"", 145 }, { 146 s: "https://jujucharms.com/name/series/badwolf", 147 err: "charm or bundle URL has malformed revision: \"badwolf\" in $URL", 148 }, { 149 s: "https://jujucharms.com/name/bad.wolf/42", 150 err: `cannot parse URL $URL: series name "bad.wolf" not valid`, 151 }, { 152 s: "https://badwolf@jujucharms.com/name/series/42", 153 err: `charm or bundle URL $URL has unrecognized parts`, 154 }, { 155 s: "https://jujucharms.com/name/series/42#bad-wolf", 156 err: `charm or bundle URL $URL has unrecognized parts`, 157 }, { 158 s: "https://jujucharms.com/name/series/42?bad=wolf", 159 err: `charm or bundle URL $URL has unrecognized parts`, 160 }, { 161 s: "bs:~user/series/name-1", 162 err: `cannot parse URL $URL: schema "bs" not valid`, 163 }, { 164 s: ":foo", 165 err: `cannot parse charm or bundle URL: $URL`, 166 }, { 167 s: "cs:~1/series/name-1", 168 err: `charm or bundle URL has invalid user name: $URL`, 169 }, { 170 s: "cs:~user", 171 err: `URL without charm or bundle name: $URL`, 172 }, { 173 s: "cs:~user/1/name-1", 174 err: `cannot parse URL $URL: series name "1" not valid`, 175 }, { 176 s: "cs:~user/series/name-1-2", 177 err: `cannot parse URL $URL: name "name-1" not valid`, 178 }, { 179 s: "cs:~user/series/name-1-name-2", 180 err: `cannot parse URL $URL: name "name-1-name" not valid`, 181 }, { 182 s: "cs:~user/series/name--name-2", 183 err: `cannot parse URL $URL: name "name--name" not valid`, 184 }, { 185 s: "cs:foo-1-2", 186 err: `cannot parse URL $URL: name "foo-1" not valid`, 187 }, { 188 s: "cs:~user/series/huh/name-1", 189 err: `charm or bundle URL has invalid form: $URL`, 190 }, { 191 s: "cs:~user/production/series/name-1", 192 err: `charm or bundle URL has invalid form: $URL`, 193 }, { 194 s: "cs:/name", 195 err: `cannot parse URL $URL: series name "" not valid`, 196 }, { 197 s: "local:~user/series/name", 198 err: `local charm or bundle URL with user name: $URL`, 199 }, { 200 s: "local:~user/name", 201 err: `local charm or bundle URL with user name: $URL`, 202 }, { 203 s: "precise/wordpress", 204 exact: "cs:precise/wordpress", 205 url: &charm.URL{"cs", "", "wordpress", -1, "precise"}, 206 }, { 207 s: "foo", 208 exact: "cs:foo", 209 url: &charm.URL{"cs", "", "foo", -1, ""}, 210 }, { 211 s: "foo-1", 212 exact: "cs:foo-1", 213 url: &charm.URL{"cs", "", "foo", 1, ""}, 214 }, { 215 s: "n0-n0-n0", 216 exact: "cs:n0-n0-n0", 217 url: &charm.URL{"cs", "", "n0-n0-n0", -1, ""}, 218 }, { 219 s: "cs:foo", 220 exact: "cs:foo", 221 url: &charm.URL{"cs", "", "foo", -1, ""}, 222 }, { 223 s: "local:foo", 224 exact: "local:foo", 225 url: &charm.URL{"local", "", "foo", -1, ""}, 226 }, { 227 s: "series/foo", 228 exact: "cs:series/foo", 229 url: &charm.URL{"cs", "", "foo", -1, "series"}, 230 }, { 231 s: "series/foo/bar", 232 err: `charm or bundle URL has invalid form: "series/foo/bar"`, 233 }, { 234 s: "cs:foo/~blah", 235 err: `cannot parse URL $URL: name "~blah" not valid`, 236 }} 237 238 func (s *URLSuite) TestParseURL(c *gc.C) { 239 for i, t := range urlTests { 240 c.Logf("test %d: %q", i, t.s) 241 242 expectStr := t.s 243 if t.exact != "" { 244 expectStr = t.exact 245 } 246 url, uerr := charm.ParseURL(t.s) 247 if t.err != "" { 248 t.err = strings.Replace(t.err, "$URL", regexp.QuoteMeta(fmt.Sprintf("%q", t.s)), -1) 249 c.Check(uerr, gc.ErrorMatches, t.err) 250 c.Check(url, gc.IsNil) 251 continue 252 } 253 c.Assert(uerr, gc.IsNil) 254 c.Check(url, gc.DeepEquals, t.url) 255 c.Check(url.String(), gc.Equals, expectStr) 256 257 // URL strings are generated as expected. Reversability is preserved 258 // with v1 URLs. 259 if t.exact != "" { 260 c.Check(url.String(), gc.Equals, t.exact) 261 } else { 262 c.Check(url.String(), gc.Equals, t.s) 263 } 264 } 265 } 266 267 var inferTests = []struct { 268 vague, exact string 269 }{ 270 {"foo", "cs:defseries/foo"}, 271 {"foo-1", "cs:defseries/foo-1"}, 272 {"n0-n0-n0", "cs:defseries/n0-n0-n0"}, 273 {"cs:foo", "cs:defseries/foo"}, 274 {"local:foo", "local:defseries/foo"}, 275 {"series/foo", "cs:series/foo"}, 276 {"cs:series/foo", "cs:series/foo"}, 277 {"local:series/foo", "local:series/foo"}, 278 {"cs:~user/foo", "cs:~user/defseries/foo"}, 279 {"cs:~user/series/foo", "cs:~user/series/foo"}, 280 {"local:~user/series/foo", "local:~user/series/foo"}, 281 {"bs:foo", "bs:defseries/foo"}, 282 {"cs:~1/foo", "cs:~1/defseries/foo"}, 283 {"cs:foo-1-2", "cs:defseries/foo-1-2"}, 284 } 285 286 func (s *URLSuite) TestInferURL(c *gc.C) { 287 for i, t := range inferTests { 288 c.Logf("test %d", i) 289 comment := gc.Commentf("InferURL(%q, %q)", t.vague, "defseries") 290 inferred, ierr := charm.InferURL(t.vague, "defseries") 291 parsed, perr := charm.ParseURL(t.exact) 292 if perr == nil { 293 c.Check(inferred, gc.DeepEquals, parsed, comment) 294 c.Check(ierr, gc.IsNil) 295 } else { 296 expect := perr.Error() 297 if t.vague != t.exact { 298 if colIdx := strings.Index(expect, ":"); colIdx > 0 { 299 expect = expect[:colIdx] 300 } 301 } 302 c.Check(ierr.Error(), gc.Matches, expect+".*", comment) 303 } 304 } 305 u, err := charm.InferURL("~blah", "defseries") 306 c.Assert(u, gc.IsNil) 307 c.Assert(err, gc.ErrorMatches, "URL without charm or bundle name: .*") 308 } 309 310 var inferNoDefaultSeriesTests = []struct { 311 vague, exact string 312 resolved bool 313 }{ 314 {"foo", "", false}, 315 {"foo-1", "", false}, 316 {"cs:foo", "", false}, 317 {"cs:~user/foo", "", false}, 318 {"series/foo", "cs:series/foo", true}, 319 {"cs:series/foo", "cs:series/foo", true}, 320 {"cs:~user/series/foo", "cs:~user/series/foo", true}, 321 } 322 323 func (s *URLSuite) TestInferURLNoDefaultSeries(c *gc.C) { 324 for i, t := range inferNoDefaultSeriesTests { 325 c.Logf("%d: %s", i, t.vague) 326 inferred, err := charm.InferURL(t.vague, "") 327 if t.exact == "" { 328 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("cannot infer charm or bundle URL for %q: charm or bundle url series is not resolved", t.vague)) 329 } else { 330 parsed, err := charm.ParseURL(t.exact) 331 c.Assert(err, gc.IsNil) 332 c.Assert(inferred, gc.DeepEquals, parsed, gc.Commentf(`InferURL(%q, "")`, t.vague)) 333 } 334 } 335 } 336 337 var validTests = []struct { 338 valid func(string) bool 339 string string 340 expect bool 341 }{ 342 343 {charm.IsValidName, "", false}, 344 {charm.IsValidName, "wordpress", true}, 345 {charm.IsValidName, "Wordpress", false}, 346 {charm.IsValidName, "word-press", true}, 347 {charm.IsValidName, "word press", false}, 348 {charm.IsValidName, "word^press", false}, 349 {charm.IsValidName, "-wordpress", false}, 350 {charm.IsValidName, "wordpress-", false}, 351 {charm.IsValidName, "wordpress2", true}, 352 {charm.IsValidName, "wordpress-2", false}, 353 {charm.IsValidName, "word2-press2", true}, 354 355 {charm.IsValidSeries, "", false}, 356 {charm.IsValidSeries, "precise", true}, 357 {charm.IsValidSeries, "Precise", false}, 358 {charm.IsValidSeries, "pre cise", false}, 359 {charm.IsValidSeries, "pre-cise", false}, 360 {charm.IsValidSeries, "pre^cise", false}, 361 {charm.IsValidSeries, "prec1se", true}, 362 {charm.IsValidSeries, "-precise", false}, 363 {charm.IsValidSeries, "precise-", false}, 364 {charm.IsValidSeries, "precise-1", false}, 365 {charm.IsValidSeries, "precise1", true}, 366 {charm.IsValidSeries, "pre-c1se", false}, 367 } 368 369 func (s *URLSuite) TestValidCheckers(c *gc.C) { 370 for i, t := range validTests { 371 c.Logf("test %d: %s", i, t.string) 372 c.Assert(t.valid(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string)) 373 } 374 } 375 376 func (s *URLSuite) TestMustParseURL(c *gc.C) { 377 url := charm.MustParseURL("cs:series/name") 378 c.Assert(url, gc.DeepEquals, &charm.URL{"cs", "", "name", -1, "series"}) 379 f := func() { charm.MustParseURL("local:@@/name") } 380 c.Assert(f, gc.PanicMatches, "cannot parse URL \"local:@@/name\": series name \"@@\" not valid") 381 f = func() { charm.MustParseURL("cs:~user") } 382 c.Assert(f, gc.PanicMatches, "URL without charm or bundle name: .*") 383 f = func() { charm.MustParseURL("cs:~user") } 384 c.Assert(f, gc.PanicMatches, "URL without charm or bundle name: .*") 385 } 386 387 func (s *URLSuite) TestWithRevision(c *gc.C) { 388 url := charm.MustParseURL("cs:series/name") 389 other := url.WithRevision(1) 390 c.Assert(url, gc.DeepEquals, &charm.URL{"cs", "", "name", -1, "series"}) 391 c.Assert(other, gc.DeepEquals, &charm.URL{"cs", "", "name", 1, "series"}) 392 393 // Should always copy. The opposite behavior is error prone. 394 c.Assert(other.WithRevision(1), gc.Not(gc.Equals), other) 395 c.Assert(other.WithRevision(1), gc.DeepEquals, other) 396 } 397 398 var codecs = []struct { 399 Name string 400 Marshal func(interface{}) ([]byte, error) 401 Unmarshal func([]byte, interface{}) error 402 }{{ 403 Name: "bson", 404 Marshal: bson.Marshal, 405 Unmarshal: bson.Unmarshal, 406 }, { 407 Name: "json", 408 Marshal: json.Marshal, 409 Unmarshal: json.Unmarshal, 410 }, { 411 Name: "yaml", 412 Marshal: yaml.Marshal, 413 Unmarshal: yaml.Unmarshal, 414 }} 415 416 func (s *URLSuite) TestURLCodecs(c *gc.C) { 417 for i, codec := range codecs { 418 c.Logf("codec %d: %v", i, codec.Name) 419 type doc struct { 420 URL *charm.URL `json:",omitempty" bson:",omitempty" yaml:",omitempty"` 421 } 422 url := charm.MustParseURL("cs:series/name") 423 v0 := doc{url} 424 data, err := codec.Marshal(v0) 425 c.Assert(err, gc.IsNil) 426 var v doc 427 err = codec.Unmarshal(data, &v) 428 c.Assert(v, gc.DeepEquals, v0) 429 430 // Check that the underlying representation 431 // is a string. 432 type strDoc struct { 433 URL string 434 } 435 var vs strDoc 436 err = codec.Unmarshal(data, &vs) 437 c.Assert(err, gc.IsNil) 438 c.Assert(vs.URL, gc.Equals, "cs:series/name") 439 440 data, err = codec.Marshal(doc{}) 441 c.Assert(err, gc.IsNil) 442 v = doc{} 443 err = codec.Unmarshal(data, &v) 444 c.Assert(err, gc.IsNil) 445 c.Assert(v.URL, gc.IsNil, gc.Commentf("data: %q", data)) 446 } 447 } 448 449 func (s *URLSuite) TestJSONGarbage(c *gc.C) { 450 // unmarshalling json gibberish 451 for _, value := range []string{":{", `"cs:{}+<"`, `"cs:~_~/f00^^&^/baaaar$%-?"`} { 452 err := json.Unmarshal([]byte(value), new(struct{ URL *charm.URL })) 453 c.Check(err, gc.NotNil) 454 } 455 } 456 457 type QuoteSuite struct{} 458 459 var _ = gc.Suite(&QuoteSuite{}) 460 461 func (s *QuoteSuite) TestUnmodified(c *gc.C) { 462 // Check that a string containing only valid 463 // chars stays unmodified. 464 in := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-" 465 out := charm.Quote(in) 466 c.Assert(out, gc.Equals, in) 467 } 468 469 func (s *QuoteSuite) TestQuote(c *gc.C) { 470 // Check that invalid chars are translated correctly. 471 in := "hello_there/how'are~you-today.sir" 472 out := charm.Quote(in) 473 c.Assert(out, gc.Equals, "hello_5f_there_2f_how_27_are_7e_you-today.sir") 474 }