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  }