github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/reference/reference_test.go (about)

     1  package reference
     2  
     3  import (
     4  	"encoding/json"
     5  	"strconv"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/docker/distribution/digest"
    10  )
    11  
    12  func TestReferenceParse(t *testing.T) {
    13  	// referenceTestcases is a unified set of testcases for
    14  	// testing the parsing of references
    15  	referenceTestcases := []struct {
    16  		// input is the repository name or name component testcase
    17  		input string
    18  		// err is the error expected from Parse, or nil
    19  		err error
    20  		// repository is the string representation for the reference
    21  		repository string
    22  		// hostname is the hostname expected in the reference
    23  		hostname string
    24  		// tag is the tag for the reference
    25  		tag string
    26  		// digest is the digest for the reference (enforces digest reference)
    27  		digest string
    28  	}{
    29  		{
    30  			input:      "test_com",
    31  			repository: "test_com",
    32  		},
    33  		{
    34  			input:      "test.com:tag",
    35  			repository: "test.com",
    36  			tag:        "tag",
    37  		},
    38  		{
    39  			input:      "test.com:5000",
    40  			repository: "test.com",
    41  			tag:        "5000",
    42  		},
    43  		{
    44  			input:      "test.com/repo:tag",
    45  			hostname:   "test.com",
    46  			repository: "test.com/repo",
    47  			tag:        "tag",
    48  		},
    49  		{
    50  			input:      "test:5000/repo",
    51  			hostname:   "test:5000",
    52  			repository: "test:5000/repo",
    53  		},
    54  		{
    55  			input:      "test:5000/repo:tag",
    56  			hostname:   "test:5000",
    57  			repository: "test:5000/repo",
    58  			tag:        "tag",
    59  		},
    60  		{
    61  			input:      "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    62  			hostname:   "test:5000",
    63  			repository: "test:5000/repo",
    64  			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    65  		},
    66  		{
    67  			input:      "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    68  			hostname:   "test:5000",
    69  			repository: "test:5000/repo",
    70  			tag:        "tag",
    71  			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    72  		},
    73  		{
    74  			input:      "test:5000/repo",
    75  			hostname:   "test:5000",
    76  			repository: "test:5000/repo",
    77  		},
    78  		{
    79  			input: "",
    80  			err:   ErrNameEmpty,
    81  		},
    82  		{
    83  			input: ":justtag",
    84  			err:   ErrReferenceInvalidFormat,
    85  		},
    86  		{
    87  			input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    88  			err:   ErrReferenceInvalidFormat,
    89  		},
    90  		{
    91  			input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
    92  			err:   digest.ErrDigestInvalidLength,
    93  		},
    94  		{
    95  			input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    96  			err:   digest.ErrDigestUnsupported,
    97  		},
    98  		{
    99  			input: strings.Repeat("a/", 128) + "a:tag",
   100  			err:   ErrNameTooLong,
   101  		},
   102  		{
   103  			input:      strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
   104  			hostname:   "a",
   105  			repository: strings.Repeat("a/", 127) + "a",
   106  			tag:        "tag-puts-this-over-max",
   107  		},
   108  		{
   109  			input: "aa/asdf$$^/aa",
   110  			err:   ErrReferenceInvalidFormat,
   111  		},
   112  		{
   113  			input:      "sub-dom1.foo.com/bar/baz/quux",
   114  			hostname:   "sub-dom1.foo.com",
   115  			repository: "sub-dom1.foo.com/bar/baz/quux",
   116  		},
   117  		{
   118  			input:      "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
   119  			hostname:   "sub-dom1.foo.com",
   120  			repository: "sub-dom1.foo.com/bar/baz/quux",
   121  			tag:        "some-long-tag",
   122  		},
   123  		{
   124  			input:      "b.gcr.io/test.example.com/my-app:test.example.com",
   125  			hostname:   "b.gcr.io",
   126  			repository: "b.gcr.io/test.example.com/my-app",
   127  			tag:        "test.example.com",
   128  		},
   129  		{
   130  			input:      "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
   131  			hostname:   "xn--n3h.com",
   132  			repository: "xn--n3h.com/myimage",
   133  			tag:        "xn--n3h.com",
   134  		},
   135  		{
   136  			input:      "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
   137  			hostname:   "xn--7o8h.com",
   138  			repository: "xn--7o8h.com/myimage",
   139  			tag:        "xn--7o8h.com",
   140  			digest:     "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   141  		},
   142  		{
   143  			input:      "foo_bar.com:8080",
   144  			repository: "foo_bar.com",
   145  			tag:        "8080",
   146  		},
   147  		{
   148  			input:      "foo/foo_bar.com:8080",
   149  			hostname:   "foo",
   150  			repository: "foo/foo_bar.com",
   151  			tag:        "8080",
   152  		},
   153  	}
   154  	for _, testcase := range referenceTestcases {
   155  		failf := func(format string, v ...interface{}) {
   156  			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
   157  			t.Fail()
   158  		}
   159  
   160  		repo, err := Parse(testcase.input)
   161  		if testcase.err != nil {
   162  			if err == nil {
   163  				failf("missing expected error: %v", testcase.err)
   164  			} else if testcase.err != err {
   165  				failf("mismatched error: got %v, expected %v", err, testcase.err)
   166  			}
   167  			continue
   168  		} else if err != nil {
   169  			failf("unexpected parse error: %v", err)
   170  			continue
   171  		}
   172  		if repo.String() != testcase.input {
   173  			failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input)
   174  		}
   175  
   176  		if named, ok := repo.(Named); ok {
   177  			if named.Name() != testcase.repository {
   178  				failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository)
   179  			}
   180  			hostname, _ := SplitHostname(named)
   181  			if hostname != testcase.hostname {
   182  				failf("unexpected hostname: got %q, expected %q", hostname, testcase.hostname)
   183  			}
   184  		} else if testcase.repository != "" || testcase.hostname != "" {
   185  			failf("expected named type, got %T", repo)
   186  		}
   187  
   188  		tagged, ok := repo.(Tagged)
   189  		if testcase.tag != "" {
   190  			if ok {
   191  				if tagged.Tag() != testcase.tag {
   192  					failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
   193  				}
   194  			} else {
   195  				failf("expected tagged type, got %T", repo)
   196  			}
   197  		} else if ok {
   198  			failf("unexpected tagged type")
   199  		}
   200  
   201  		digested, ok := repo.(Digested)
   202  		if testcase.digest != "" {
   203  			if ok {
   204  				if digested.Digest().String() != testcase.digest {
   205  					failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
   206  				}
   207  			} else {
   208  				failf("expected digested type, got %T", repo)
   209  			}
   210  		} else if ok {
   211  			failf("unexpected digested type")
   212  		}
   213  
   214  	}
   215  }
   216  
   217  // TestWithNameFailure tests cases where WithName should fail. Cases where it
   218  // should succeed are covered by TestSplitHostname, below.
   219  func TestWithNameFailure(t *testing.T) {
   220  	testcases := []struct {
   221  		input string
   222  		err   error
   223  	}{
   224  		{
   225  			input: "",
   226  			err:   ErrNameEmpty,
   227  		},
   228  		{
   229  			input: ":justtag",
   230  			err:   ErrReferenceInvalidFormat,
   231  		},
   232  		{
   233  			input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   234  			err:   ErrReferenceInvalidFormat,
   235  		},
   236  		{
   237  			input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   238  			err:   ErrReferenceInvalidFormat,
   239  		},
   240  		{
   241  			input: strings.Repeat("a/", 128) + "a:tag",
   242  			err:   ErrNameTooLong,
   243  		},
   244  		{
   245  			input: "aa/asdf$$^/aa",
   246  			err:   ErrReferenceInvalidFormat,
   247  		},
   248  	}
   249  	for _, testcase := range testcases {
   250  		failf := func(format string, v ...interface{}) {
   251  			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
   252  			t.Fail()
   253  		}
   254  
   255  		_, err := WithName(testcase.input)
   256  		if err == nil {
   257  			failf("no error parsing name. expected: %s", testcase.err)
   258  		}
   259  	}
   260  }
   261  
   262  func TestSplitHostname(t *testing.T) {
   263  	testcases := []struct {
   264  		input    string
   265  		hostname string
   266  		name     string
   267  	}{
   268  		{
   269  			input:    "test.com/foo",
   270  			hostname: "test.com",
   271  			name:     "foo",
   272  		},
   273  		{
   274  			input:    "test_com/foo",
   275  			hostname: "",
   276  			name:     "test_com/foo",
   277  		},
   278  		{
   279  			input:    "test:8080/foo",
   280  			hostname: "test:8080",
   281  			name:     "foo",
   282  		},
   283  		{
   284  			input:    "test.com:8080/foo",
   285  			hostname: "test.com:8080",
   286  			name:     "foo",
   287  		},
   288  		{
   289  			input:    "test-com:8080/foo",
   290  			hostname: "test-com:8080",
   291  			name:     "foo",
   292  		},
   293  		{
   294  			input:    "xn--n3h.com:18080/foo",
   295  			hostname: "xn--n3h.com:18080",
   296  			name:     "foo",
   297  		},
   298  	}
   299  	for _, testcase := range testcases {
   300  		failf := func(format string, v ...interface{}) {
   301  			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
   302  			t.Fail()
   303  		}
   304  
   305  		named, err := WithName(testcase.input)
   306  		if err != nil {
   307  			failf("error parsing name: %s", err)
   308  		}
   309  		hostname, name := SplitHostname(named)
   310  		if hostname != testcase.hostname {
   311  			failf("unexpected hostname: got %q, expected %q", hostname, testcase.hostname)
   312  		}
   313  		if name != testcase.name {
   314  			failf("unexpected name: got %q, expected %q", name, testcase.name)
   315  		}
   316  	}
   317  }
   318  
   319  type serializationType struct {
   320  	Description string
   321  	Field       Field
   322  }
   323  
   324  func TestSerialization(t *testing.T) {
   325  	testcases := []struct {
   326  		description string
   327  		input       string
   328  		name        string
   329  		tag         string
   330  		digest      string
   331  		err         error
   332  	}{
   333  		{
   334  			description: "empty value",
   335  			err:         ErrNameEmpty,
   336  		},
   337  		{
   338  			description: "just a name",
   339  			input:       "example.com:8000/named",
   340  			name:        "example.com:8000/named",
   341  		},
   342  		{
   343  			description: "name with a tag",
   344  			input:       "example.com:8000/named:tagged",
   345  			name:        "example.com:8000/named",
   346  			tag:         "tagged",
   347  		},
   348  		{
   349  			description: "name with digest",
   350  			input:       "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
   351  			name:        "other.com/named",
   352  			digest:      "sha256:1234567890098765432112345667890098765432112345667890098765432112",
   353  		},
   354  	}
   355  	for _, testcase := range testcases {
   356  		failf := func(format string, v ...interface{}) {
   357  			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
   358  			t.Fail()
   359  		}
   360  
   361  		m := map[string]string{
   362  			"Description": testcase.description,
   363  			"Field":       testcase.input,
   364  		}
   365  		b, err := json.Marshal(m)
   366  		if err != nil {
   367  			failf("error marshalling: %v", err)
   368  		}
   369  		t := serializationType{}
   370  
   371  		if err := json.Unmarshal(b, &t); err != nil {
   372  			if testcase.err == nil {
   373  				failf("error unmarshalling: %v", err)
   374  			}
   375  			if err != testcase.err {
   376  				failf("wrong error, expected %v, got %v", testcase.err, err)
   377  			}
   378  
   379  			continue
   380  		} else if testcase.err != nil {
   381  			failf("expected error unmarshalling: %v", testcase.err)
   382  		}
   383  
   384  		if t.Description != testcase.description {
   385  			failf("wrong description, expected %q, got %q", testcase.description, t.Description)
   386  		}
   387  
   388  		ref := t.Field.Reference()
   389  
   390  		if named, ok := ref.(Named); ok {
   391  			if named.Name() != testcase.name {
   392  				failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name)
   393  			}
   394  		} else if testcase.name != "" {
   395  			failf("expected named type, got %T", ref)
   396  		}
   397  
   398  		tagged, ok := ref.(Tagged)
   399  		if testcase.tag != "" {
   400  			if ok {
   401  				if tagged.Tag() != testcase.tag {
   402  					failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
   403  				}
   404  			} else {
   405  				failf("expected tagged type, got %T", ref)
   406  			}
   407  		} else if ok {
   408  			failf("unexpected tagged type")
   409  		}
   410  
   411  		digested, ok := ref.(Digested)
   412  		if testcase.digest != "" {
   413  			if ok {
   414  				if digested.Digest().String() != testcase.digest {
   415  					failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
   416  				}
   417  			} else {
   418  				failf("expected digested type, got %T", ref)
   419  			}
   420  		} else if ok {
   421  			failf("unexpected digested type")
   422  		}
   423  
   424  		t = serializationType{
   425  			Description: testcase.description,
   426  			Field:       AsField(ref),
   427  		}
   428  
   429  		b2, err := json.Marshal(t)
   430  		if err != nil {
   431  			failf("error marshing serialization type: %v", err)
   432  		}
   433  
   434  		if string(b) != string(b2) {
   435  			failf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
   436  		}
   437  
   438  		// Ensure t.Field is not implementing "Reference" directly, getting
   439  		// around the Reference type system
   440  		var fieldInterface interface{} = t.Field
   441  		if _, ok := fieldInterface.(Reference); ok {
   442  			failf("field should not implement Reference interface")
   443  		}
   444  
   445  	}
   446  }
   447  
   448  func TestWithTag(t *testing.T) {
   449  	testcases := []struct {
   450  		name     string
   451  		tag      string
   452  		combined string
   453  	}{
   454  		{
   455  			name:     "test.com/foo",
   456  			tag:      "tag",
   457  			combined: "test.com/foo:tag",
   458  		},
   459  		{
   460  			name:     "foo",
   461  			tag:      "tag2",
   462  			combined: "foo:tag2",
   463  		},
   464  		{
   465  			name:     "test.com:8000/foo",
   466  			tag:      "tag4",
   467  			combined: "test.com:8000/foo:tag4",
   468  		},
   469  		{
   470  			name:     "test.com:8000/foo",
   471  			tag:      "TAG5",
   472  			combined: "test.com:8000/foo:TAG5",
   473  		},
   474  	}
   475  	for _, testcase := range testcases {
   476  		failf := func(format string, v ...interface{}) {
   477  			t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
   478  			t.Fail()
   479  		}
   480  
   481  		named, err := WithName(testcase.name)
   482  		if err != nil {
   483  			failf("error parsing name: %s", err)
   484  		}
   485  		tagged, err := WithTag(named, testcase.tag)
   486  		if err != nil {
   487  			failf("WithTag failed: %s", err)
   488  		}
   489  		if tagged.String() != testcase.combined {
   490  			failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined)
   491  		}
   492  	}
   493  }
   494  
   495  func TestWithDigest(t *testing.T) {
   496  	testcases := []struct {
   497  		name     string
   498  		digest   digest.Digest
   499  		combined string
   500  	}{
   501  		{
   502  			name:     "test.com/foo",
   503  			digest:   "sha256:1234567890098765432112345667890098765",
   504  			combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
   505  		},
   506  		{
   507  			name:     "foo",
   508  			digest:   "sha256:1234567890098765432112345667890098765",
   509  			combined: "foo@sha256:1234567890098765432112345667890098765",
   510  		},
   511  		{
   512  			name:     "test.com:8000/foo",
   513  			digest:   "sha256:1234567890098765432112345667890098765",
   514  			combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
   515  		},
   516  	}
   517  	for _, testcase := range testcases {
   518  		failf := func(format string, v ...interface{}) {
   519  			t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
   520  			t.Fail()
   521  		}
   522  
   523  		named, err := WithName(testcase.name)
   524  		if err != nil {
   525  			failf("error parsing name: %s", err)
   526  		}
   527  		digested, err := WithDigest(named, testcase.digest)
   528  		if err != nil {
   529  			failf("WithDigest failed: %s", err)
   530  		}
   531  		if digested.String() != testcase.combined {
   532  			failf("unexpected: got %q, expected %q", digested.String(), testcase.combined)
   533  		}
   534  	}
   535  }