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  }