1
// Copyright 2011, 2012, 2013 Canonical Ltd.
2
// Licensed under the LGPLv3, see LICENCE file for details.
12
gc "gopkg.in/check.v1"
13
"gopkg.in/mgo.v2/bson"
16
"gopkg.in/juju/charm.v6-unstable"
19
type URLSuite struct{}
21
var _ = gc.Suite(&URLSuite{})
23
var urlTests = []struct {
28
s: "cs:~user/series/name",
29
url: &charm.URL{"cs", "user", "name", -1, "series", ""},
31
s: "cs:~user/series/name-0",
32
url: &charm.URL{"cs", "user", "name", 0, "series", ""},
35
url: &charm.URL{"cs", "", "name", -1, "series", ""},
37
s: "cs:series/name-42",
38
url: &charm.URL{"cs", "", "name", 42, "series", ""},
40
s: "local:series/name-1",
41
url: &charm.URL{"local", "", "name", 1, "series", ""},
43
s: "local:series/name",
44
url: &charm.URL{"local", "", "name", -1, "series", ""},
46
s: "local:series/n0-0n-n0",
47
url: &charm.URL{"local", "", "n0-0n-n0", -1, "series", ""},
50
url: &charm.URL{"cs", "user", "name", -1, "", ""},
53
url: &charm.URL{"cs", "", "name", -1, "", ""},
56
url: &charm.URL{"local", "", "name", -1, "", ""},
58
s: "cs:~user/development/series/name-0",
59
url: &charm.URL{"cs", "user", "name", 0, "series", charm.DevelopmentChannel},
61
s: "cs:~user/development/series/name-0",
62
url: &charm.URL{"cs", "user", "name", 0, "series", charm.DevelopmentChannel},
64
s: "cs:development/series/name",
65
url: &charm.URL{"cs", "", "name", -1, "series", charm.DevelopmentChannel},
67
s: "cs:development/series/name-42",
68
url: &charm.URL{"cs", "", "name", 42, "series", charm.DevelopmentChannel},
70
s: "cs:~user/development/name",
71
url: &charm.URL{"cs", "user", "name", -1, "", charm.DevelopmentChannel},
73
s: "cs:development/name",
74
url: &charm.URL{"cs", "", "name", -1, "", charm.DevelopmentChannel},
76
s: "http://jujucharms.com/u/user/name/series/1",
77
url: &charm.URL{"cs", "user", "name", 1, "series", ""},
78
exact: "cs:~user/series/name-1",
80
s: "http://www.jujucharms.com/u/user/name/series/1",
81
url: &charm.URL{"cs", "user", "name", 1, "series", ""},
82
exact: "cs:~user/series/name-1",
84
s: "https://www.jujucharms.com/u/user/name/series/1",
85
url: &charm.URL{"cs", "user", "name", 1, "series", ""},
86
exact: "cs:~user/series/name-1",
88
s: "https://jujucharms.com/u/user/name/series/1",
89
url: &charm.URL{"cs", "user", "name", 1, "series", ""},
90
exact: "cs:~user/series/name-1",
92
s: "https://jujucharms.com/u/user/name/series",
93
url: &charm.URL{"cs", "user", "name", -1, "series", ""},
94
exact: "cs:~user/series/name",
96
s: "https://jujucharms.com/u/user/name/1",
97
url: &charm.URL{"cs", "user", "name", 1, "", ""},
98
exact: "cs:~user/name-1",
100
s: "https://jujucharms.com/u/user/name",
101
url: &charm.URL{"cs", "user", "name", -1, "", ""},
102
exact: "cs:~user/name",
104
s: "https://jujucharms.com/name",
105
url: &charm.URL{"cs", "", "name", -1, "", ""},
108
s: "https://jujucharms.com/name/series",
109
url: &charm.URL{"cs", "", "name", -1, "series", ""},
110
exact: "cs:series/name",
112
s: "https://jujucharms.com/name/1",
113
url: &charm.URL{"cs", "", "name", 1, "", ""},
116
s: "https://jujucharms.com/name/series/1",
117
url: &charm.URL{"cs", "", "name", 1, "series", ""},
118
exact: "cs:series/name-1",
120
s: "https://jujucharms.com/u/user/name/series/1/",
121
url: &charm.URL{"cs", "user", "name", 1, "series", ""},
122
exact: "cs:~user/series/name-1",
124
s: "https://jujucharms.com/u/user/name/series/",
125
url: &charm.URL{"cs", "user", "name", -1, "series", ""},
126
exact: "cs:~user/series/name",
128
s: "https://jujucharms.com/u/user/name/1/",
129
url: &charm.URL{"cs", "user", "name", 1, "", ""},
130
exact: "cs:~user/name-1",
132
s: "https://jujucharms.com/u/user/name/",
133
url: &charm.URL{"cs", "user", "name", -1, "", ""},
134
exact: "cs:~user/name",
136
s: "https://jujucharms.com/name/",
137
url: &charm.URL{"cs", "", "name", -1, "", ""},
140
s: "https://jujucharms.com/name/series/",
141
url: &charm.URL{"cs", "", "name", -1, "series", ""},
142
exact: "cs:series/name",
144
s: "https://jujucharms.com/name/1/",
145
url: &charm.URL{"cs", "", "name", 1, "", ""},
148
s: "https://jujucharms.com/name/series/1/",
149
url: &charm.URL{"cs", "", "name", 1, "series", ""},
150
exact: "cs:series/name-1",
152
s: "https://jujucharms.com/u/user/development/name/series/1",
153
url: &charm.URL{"cs", "user", "name", 1, "series", charm.DevelopmentChannel},
154
exact: "cs:~user/development/series/name-1",
156
s: "https://jujucharms.com/u/user/development/name/series",
157
url: &charm.URL{"cs", "user", "name", -1, "series", charm.DevelopmentChannel},
158
exact: "cs:~user/development/series/name",
160
s: "https://jujucharms.com/u/user/development/name/1",
161
url: &charm.URL{"cs", "user", "name", 1, "", charm.DevelopmentChannel},
162
exact: "cs:~user/development/name-1",
164
s: "https://jujucharms.com/u/user/development/name",
165
url: &charm.URL{"cs", "user", "name", -1, "", charm.DevelopmentChannel},
166
exact: "cs:~user/development/name",
168
s: "https://jujucharms.com/development/name",
169
url: &charm.URL{"cs", "", "name", -1, "", charm.DevelopmentChannel},
170
exact: "cs:development/name",
172
s: "https://jujucharms.com/development/name/series",
173
url: &charm.URL{"cs", "", "name", -1, "series", charm.DevelopmentChannel},
174
exact: "cs:development/series/name",
176
s: "https://jujucharms.com/development/name/1",
177
url: &charm.URL{"cs", "", "name", 1, "", charm.DevelopmentChannel},
178
exact: "cs:development/name-1",
180
s: "https://jujucharms.com/development/name/series/1",
181
url: &charm.URL{"cs", "", "name", 1, "series", charm.DevelopmentChannel},
182
exact: "cs:development/series/name-1",
184
s: "https://jujucharms.com/u/user/development/name/series/",
185
url: &charm.URL{"cs", "user", "name", -1, "series", charm.DevelopmentChannel},
186
exact: "cs:~user/development/series/name",
188
s: "https://jujucharms.com/u/user/development/name/1/",
189
url: &charm.URL{"cs", "user", "name", 1, "", charm.DevelopmentChannel},
190
exact: "cs:~user/development/name-1",
192
s: "https://jujucharms.com/u/user/development/name/",
193
url: &charm.URL{"cs", "user", "name", -1, "", charm.DevelopmentChannel},
194
exact: "cs:~user/development/name",
196
s: "https://jujucharms.com/",
197
err: `URL has invalid charm or bundle name: $URL`,
199
s: "https://jujucharms.com/bad.wolf",
200
err: `URL has invalid charm or bundle name: $URL`,
202
s: "https://jujucharms.com/u/",
203
err: "charm or bundle URL $URL malformed, expected \"/u/<user>/<name>\"",
205
s: "https://jujucharms.com/u/badwolf",
206
err: "charm or bundle URL $URL malformed, expected \"/u/<user>/<name>\"",
208
s: "https://jujucharms.com/name/series/badwolf",
209
err: "charm or bundle URL has malformed revision: \"badwolf\" in $URL",
211
s: "https://jujucharms.com/name/bad.wolf/42",
212
err: `charm or bundle URL has invalid series: $URL`,
214
s: "https://badwolf@jujucharms.com/name/series/42",
215
err: `charm or bundle URL $URL has unrecognized parts`,
217
s: "https://jujucharms.com/name/series/42#bad-wolf",
218
err: `charm or bundle URL $URL has unrecognized parts`,
220
s: "https://jujucharms.com/name/series/42?bad=wolf",
221
err: `charm or bundle URL $URL has unrecognized parts`,
223
s: "bs:~user/series/name-1",
224
err: `charm or bundle URL has invalid schema: $URL`,
227
err: `cannot parse charm or bundle URL: $URL`,
229
s: "cs:~1/series/name-1",
230
err: `charm or bundle URL has invalid user name: $URL`,
233
err: `URL without charm or bundle name: $URL`,
235
s: "cs:~user/1/name-1",
236
err: `charm or bundle URL has invalid series: $URL`,
238
s: "cs:~user/series/name-1-2",
239
err: `URL has invalid charm or bundle name: $URL`,
241
s: "cs:~user/series/name-1-name-2",
242
err: `URL has invalid charm or bundle name: $URL`,
244
s: "cs:~user/series/name--name-2",
245
err: `URL has invalid charm or bundle name: $URL`,
248
err: `URL has invalid charm or bundle name: $URL`,
250
s: "cs:~user/series/huh/name-1",
251
err: `charm or bundle URL has invalid form: $URL`,
253
s: "cs:~user/production/series/name-1",
254
err: `charm or bundle URL has invalid form: $URL`,
256
s: "cs:~user/development/series/badwolf/name-1",
257
err: `charm or bundle URL has invalid form: $URL`,
260
err: `charm or bundle URL has invalid series: $URL`,
262
s: "local:~user/series/name",
263
err: `local charm or bundle URL with user name: $URL`,
265
s: "local:~user/name",
266
err: `local charm or bundle URL with user name: $URL`,
268
s: "local:development/name",
269
err: `local charm or bundle URL with channel: $URL`,
271
s: "local:development/series/name-1",
272
err: `local charm or bundle URL with channel: $URL`,
274
s: "precise/wordpress",
275
exact: "cs:precise/wordpress",
276
url: &charm.URL{"cs", "", "wordpress", -1, "precise", ""},
280
url: &charm.URL{"cs", "", "foo", -1, "", ""},
284
url: &charm.URL{"cs", "", "foo", 1, "", ""},
287
exact: "cs:n0-n0-n0",
288
url: &charm.URL{"cs", "", "n0-n0-n0", -1, "", ""},
292
url: &charm.URL{"cs", "", "foo", -1, "", ""},
296
url: &charm.URL{"local", "", "foo", -1, "", ""},
299
exact: "cs:series/foo",
300
url: &charm.URL{"cs", "", "foo", -1, "series", ""},
302
s: "development/foo",
303
exact: "cs:development/foo",
304
url: &charm.URL{"cs", "", "foo", -1, "", charm.DevelopmentChannel},
306
s: "development/foo-1",
307
exact: "cs:development/foo-1",
308
url: &charm.URL{"cs", "", "foo", 1, "", charm.DevelopmentChannel},
310
s: "development/n0-n0-n0",
311
exact: "cs:development/n0-n0-n0",
312
url: &charm.URL{"cs", "", "n0-n0-n0", -1, "", charm.DevelopmentChannel},
314
s: "development/series/foo",
315
exact: "cs:development/series/foo",
316
url: &charm.URL{"cs", "", "foo", -1, "series", charm.DevelopmentChannel},
319
err: `charm or bundle URL has invalid form: "series/foo/bar"`,
322
err: `URL has invalid charm or bundle name: "cs:foo/~blah"`,
325
func (s *URLSuite) TestParseURL(c *gc.C) {
326
for i, t := range urlTests {
327
c.Logf("test %d: %q", i, t.s)
333
url, uerr := charm.ParseURL(t.s)
335
t.err = strings.Replace(t.err, "$URL", regexp.QuoteMeta(fmt.Sprintf("%q", t.s)), -1)
336
c.Assert(uerr, gc.ErrorMatches, t.err)
337
c.Assert(url, gc.IsNil)
340
c.Assert(uerr, gc.IsNil)
341
c.Assert(url, gc.DeepEquals, t.url)
342
c.Assert(url.String(), gc.Equals, expectStr)
344
// URL strings are generated as expected. Reversability is preserved
347
c.Check(url.String(), gc.Equals, t.exact)
349
c.Check(url.String(), gc.Equals, t.s)
354
var inferTests = []struct {
357
{"foo", "cs:defseries/foo"},
358
{"foo-1", "cs:defseries/foo-1"},
359
{"n0-n0-n0", "cs:defseries/n0-n0-n0"},
360
{"cs:foo", "cs:defseries/foo"},
361
{"local:foo", "local:defseries/foo"},
362
{"series/foo", "cs:series/foo"},
363
{"cs:series/foo", "cs:series/foo"},
364
{"local:series/foo", "local:series/foo"},
365
{"cs:~user/foo", "cs:~user/defseries/foo"},
366
{"cs:~user/series/foo", "cs:~user/series/foo"},
367
{"local:~user/series/foo", "local:~user/series/foo"},
368
{"bs:foo", "bs:defseries/foo"},
369
{"cs:~1/foo", "cs:~1/defseries/foo"},
370
{"cs:foo-1-2", "cs:defseries/foo-1-2"},
371
{"development/foo", "cs:development/defseries/foo"},
372
{"development/foo-1", "cs:development/defseries/foo-1"},
373
{"development/series/foo", "cs:development/series/foo"},
374
{"local:development/series/foo", "local:development/series/foo"},
375
{"cs:~user/development/foo", "cs:~user/development/defseries/foo"},
376
{"local:~user/development/series/foo", "local:~user/development/series/foo"},
377
{"cs:~1/development/foo", "cs:~1/development/defseries/foo"},
380
func (s *URLSuite) TestInferURL(c *gc.C) {
381
for i, t := range inferTests {
383
comment := gc.Commentf("InferURL(%q, %q)", t.vague, "defseries")
384
inferred, ierr := charm.InferURL(t.vague, "defseries")
385
parsed, perr := charm.ParseURL(t.exact)
387
c.Check(inferred, gc.DeepEquals, parsed, comment)
388
c.Check(ierr, gc.IsNil)
390
expect := perr.Error()
391
if t.vague != t.exact {
392
if colIdx := strings.Index(expect, ":"); colIdx > 0 {
393
expect = expect[:colIdx]
396
c.Check(ierr.Error(), gc.Matches, expect+".*", comment)
399
u, err := charm.InferURL("~blah", "defseries")
400
c.Assert(u, gc.IsNil)
401
c.Assert(err, gc.ErrorMatches, "URL without charm or bundle name: .*")
404
var inferNoDefaultSeriesTests = []struct {
409
{"foo-1", "", false},
410
{"cs:foo", "", false},
411
{"cs:~user/foo", "", false},
412
{"series/foo", "cs:series/foo", true},
413
{"cs:series/foo", "cs:series/foo", true},
414
{"cs:~user/series/foo", "cs:~user/series/foo", true},
415
{"development/foo", "", false},
416
{"development/foo-1", "", false},
417
{"cs:development/foo", "", false},
418
{"cs:~user/development/foo", "", false},
419
{"development/series/foo", "cs:development/series/foo", true},
420
{"cs:development/series/foo", "cs:development/series/foo", true},
421
{"cs:~user/development/series/foo", "cs:~user/development/series/foo", true},
424
func (s *URLSuite) TestInferURLNoDefaultSeries(c *gc.C) {
425
for i, t := range inferNoDefaultSeriesTests {
426
c.Logf("%d: %s", i, t.vague)
427
inferred, err := charm.InferURL(t.vague, "")
429
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))
431
parsed, err := charm.ParseURL(t.exact)
432
c.Assert(err, gc.IsNil)
433
c.Assert(inferred, gc.DeepEquals, parsed, gc.Commentf(`InferURL(%q, "")`, t.vague))
438
var validTests = []struct {
439
valid func(string) bool
444
{charm.IsValidName, "", false},
445
{charm.IsValidName, "wordpress", true},
446
{charm.IsValidName, "Wordpress", false},
447
{charm.IsValidName, "word-press", true},
448
{charm.IsValidName, "word press", false},
449
{charm.IsValidName, "word^press", false},
450
{charm.IsValidName, "-wordpress", false},
451
{charm.IsValidName, "wordpress-", false},
452
{charm.IsValidName, "wordpress2", true},
453
{charm.IsValidName, "wordpress-2", false},
454
{charm.IsValidName, "word2-press2", true},
456
{charm.IsValidSeries, "", false},
457
{charm.IsValidSeries, "precise", true},
458
{charm.IsValidSeries, "Precise", false},
459
{charm.IsValidSeries, "pre cise", false},
460
{charm.IsValidSeries, "pre-cise", false},
461
{charm.IsValidSeries, "pre^cise", false},
462
{charm.IsValidSeries, "prec1se", true},
463
{charm.IsValidSeries, "-precise", false},
464
{charm.IsValidSeries, "precise-", false},
465
{charm.IsValidSeries, "precise-1", false},
466
{charm.IsValidSeries, "precise1", true},
467
{charm.IsValidSeries, "pre-c1se", false},
470
func (s *URLSuite) TestValidCheckers(c *gc.C) {
471
for i, t := range validTests {
472
c.Logf("test %d: %s", i, t.string)
473
c.Assert(t.valid(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
477
var isValidChannelTests = []struct {
478
channel charm.Channel
481
channel: charm.DevelopmentChannel,
486
channel: "-development",
491
func (s *URLSuite) TestIsValidChannel(c *gc.C) {
492
for i, t := range isValidChannelTests {
493
c.Logf("test %d: %s", i, t.channel)
494
c.Assert(charm.IsValidChannel(t.channel), gc.Equals, t.expect, gc.Commentf("%s", t.channel))
498
func (s *URLSuite) TestMustParseURL(c *gc.C) {
499
url := charm.MustParseURL("cs:series/name")
500
c.Assert(url, gc.DeepEquals, &charm.URL{"cs", "", "name", -1, "series", ""})
501
f := func() { charm.MustParseURL("local:@@/name") }
502
c.Assert(f, gc.PanicMatches, "charm or bundle URL has invalid series: .*")
503
f = func() { charm.MustParseURL("cs:~user") }
504
c.Assert(f, gc.PanicMatches, "URL without charm or bundle name: .*")
505
f = func() { charm.MustParseURL("cs:~user") }
506
c.Assert(f, gc.PanicMatches, "URL without charm or bundle name: .*")
509
func (s *URLSuite) TestWithRevision(c *gc.C) {
510
url := charm.MustParseURL("cs:series/name")
511
other := url.WithRevision(1)
512
c.Assert(url, gc.DeepEquals, &charm.URL{"cs", "", "name", -1, "series", ""})
513
c.Assert(other, gc.DeepEquals, &charm.URL{"cs", "", "name", 1, "series", ""})
515
// Should always copy. The opposite behavior is error prone.
516
c.Assert(other.WithRevision(1), gc.Not(gc.Equals), other)
517
c.Assert(other.WithRevision(1), gc.DeepEquals, other)
520
func (s *URLSuite) TestWithChannel(c *gc.C) {
521
url := charm.MustParseURL("cs:series/name")
522
other := url.WithChannel("development")
523
c.Assert(url, gc.DeepEquals, &charm.URL{"cs", "", "name", -1, "series", ""})
524
c.Assert(other, gc.DeepEquals, &charm.URL{"cs", "", "name", -1, "series", "development"})
526
// Should always copy. The opposite behavior is error prone.
527
c.Assert(other.WithRevision(1), gc.Not(gc.Equals), other)
529
// Set the channel back to empty.
530
other = url.WithChannel("")
531
c.Assert(other, gc.DeepEquals, &charm.URL{"cs", "", "name", -1, "series", ""})
534
var codecs = []struct {
536
Marshal func(interface{}) ([]byte, error)
537
Unmarshal func([]byte, interface{}) error
540
Marshal: bson.Marshal,
541
Unmarshal: bson.Unmarshal,
544
Marshal: json.Marshal,
545
Unmarshal: json.Unmarshal,
548
Marshal: yaml.Marshal,
549
Unmarshal: yaml.Unmarshal,
552
func (s *URLSuite) TestURLCodecs(c *gc.C) {
553
for i, codec := range codecs {
554
c.Logf("codec %d: %v", i, codec.Name)
556
URL *charm.URL `json:",omitempty" bson:",omitempty" yaml:",omitempty"`
558
url := charm.MustParseURL("cs:series/name")
560
data, err := codec.Marshal(v0)
561
c.Assert(err, gc.IsNil)
563
err = codec.Unmarshal(data, &v)
564
c.Assert(v, gc.DeepEquals, v0)
566
// Check that the underlying representation
572
err = codec.Unmarshal(data, &vs)
573
c.Assert(err, gc.IsNil)
574
c.Assert(vs.URL, gc.Equals, "cs:series/name")
576
data, err = codec.Marshal(doc{})
577
c.Assert(err, gc.IsNil)
579
err = codec.Unmarshal(data, &v)
580
c.Assert(err, gc.IsNil)
581
c.Assert(v.URL, gc.IsNil, gc.Commentf("data: %q", data))
585
func (s *URLSuite) TestJSONGarbage(c *gc.C) {
586
// unmarshalling json gibberish
587
for _, value := range []string{":{", `"cs:{}+<"`, `"cs:~_~/f00^^&^/baaaar$%-?"`} {
588
err := json.Unmarshal([]byte(value), new(struct{ URL *charm.URL }))
589
c.Check(err, gc.NotNil)
593
type QuoteSuite struct{}
595
var _ = gc.Suite(&QuoteSuite{})
597
func (s *QuoteSuite) TestUnmodified(c *gc.C) {
598
// Check that a string containing only valid
599
// chars stays unmodified.
600
in := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
601
out := charm.Quote(in)
602
c.Assert(out, gc.Equals, in)
605
func (s *QuoteSuite) TestQuote(c *gc.C) {
606
// Check that invalid chars are translated correctly.
607
in := "hello_there/how'are~you-today.sir"
608
out := charm.Quote(in)
609
c.Assert(out, gc.Equals, "hello_5f_there_2f_how_27_are_7e_you-today.sir")