knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/url_test.go (about)

     1  /*
     2  Copyright 2019 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package apis
    18  
    19  import (
    20  	"encoding/json"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"k8s.io/apimachinery/pkg/api/equality"
    26  )
    27  
    28  func TestParseURL(t *testing.T) {
    29  	testCases := []struct {
    30  		name      string
    31  		t         string
    32  		want      *URL
    33  		wantEmpty bool
    34  		wantErr   bool
    35  	}{{
    36  		name:      "empty string",
    37  		want:      nil,
    38  		wantEmpty: true,
    39  	}, {
    40  		name:      "invalid format",
    41  		t:         "💩://error",
    42  		want:      nil,
    43  		wantEmpty: true,
    44  		wantErr:   true,
    45  	}, {
    46  		name: "relative",
    47  		t:    "/path/to/something",
    48  		want: &URL{
    49  			Path: "/path/to/something",
    50  		},
    51  	}, {
    52  		name: "url",
    53  		t:    "http://path/to/something",
    54  		want: &URL{
    55  			Scheme: "http",
    56  			Host:   "path",
    57  			Path:   "/to/something",
    58  		},
    59  	}, {
    60  		name: "simplehttp",
    61  		t:    "http://foo",
    62  		want: HTTP("foo"),
    63  	}, {
    64  		name: "simplehttps",
    65  		t:    "https://foo",
    66  		want: HTTPS("foo"),
    67  	}}
    68  	for _, tc := range testCases {
    69  		t.Run(tc.name, func(t *testing.T) {
    70  			got, err := ParseURL(tc.t)
    71  			if err != nil {
    72  				if !tc.wantErr {
    73  					t.Fatal("ParseURL() =", err)
    74  				}
    75  				return
    76  			} else if tc.wantErr {
    77  				t.Fatalf("ParseURL() = %v, wanted error", got)
    78  			}
    79  
    80  			if tc.wantEmpty != got.IsEmpty() {
    81  				t.Errorf("IsEmpty(%v) = %t, wanted %t", got, got.IsEmpty(), tc.wantEmpty)
    82  			}
    83  
    84  			if diff := cmp.Diff(tc.want, got); diff != "" {
    85  				t.Errorf("Unexpected object (-want, +got) =\n%s", diff)
    86  			}
    87  		})
    88  	}
    89  }
    90  
    91  func TestJSONMarshalURL(t *testing.T) {
    92  	testCases := []struct {
    93  		name string
    94  		t    string
    95  		want []byte
    96  	}{{
    97  		name: "empty",
    98  	}, {
    99  		name: "empty string",
   100  		t:    "",
   101  	}, {
   102  		name: "invalid url",
   103  		t:    "not a url",
   104  		want: []byte(`"not%20a%20url"`),
   105  	}, {
   106  		name: "relative format",
   107  		t:    "/path/to/something",
   108  		want: []byte(`"/path/to/something"`),
   109  	}}
   110  	for _, tc := range testCases {
   111  		t.Run(tc.name, func(t *testing.T) {
   112  			var got []byte
   113  			tt, err := ParseURL(tc.t)
   114  			if err != nil {
   115  				t.Fatal("ParseURL() =", err)
   116  			}
   117  			if tt != nil {
   118  				got, _ = tt.MarshalJSON()
   119  			}
   120  
   121  			if diff := cmp.Diff(tc.want, got); diff != "" {
   122  				t.Log("got:", string(got))
   123  				t.Errorf("unexpected object (-want, +got) =\n%s", diff)
   124  			}
   125  		})
   126  	}
   127  }
   128  
   129  func TestJSONUnmarshalURL(t *testing.T) {
   130  	testCases := map[string]struct {
   131  		b       []byte
   132  		want    *URL
   133  		wantErr string
   134  	}{
   135  		"empty": {
   136  			wantErr: "unexpected end of JSON input",
   137  		},
   138  		"invalid format": {
   139  			b:       []byte("%"),
   140  			wantErr: "invalid character '%' looking for beginning of value",
   141  		},
   142  		"relative": {
   143  			b: []byte(`"/path/to/something"`),
   144  			want: &URL{
   145  				Path: "/path/to/something",
   146  			},
   147  		},
   148  		"url": {
   149  			b: []byte(`"http://path/to/something"`),
   150  			want: &URL{
   151  				Scheme: "http",
   152  				Host:   "path",
   153  				Path:   "/to/something",
   154  			},
   155  		},
   156  	}
   157  	for n, tc := range testCases {
   158  		t.Run(n, func(t *testing.T) {
   159  			got := &URL{}
   160  			err := got.UnmarshalJSON(tc.b)
   161  
   162  			if tc.wantErr != "" || err != nil {
   163  				var gotErr string
   164  				if err != nil {
   165  					gotErr = err.Error()
   166  				}
   167  				if diff := cmp.Diff(tc.wantErr, gotErr); diff != "" {
   168  					t.Error("unexpected error (-want, +got) =", diff)
   169  				}
   170  				return
   171  			}
   172  
   173  			if diff := cmp.Diff(tc.want, got); diff != "" {
   174  				t.Error("unexpected object (-want, +got) =", diff)
   175  			}
   176  		})
   177  	}
   178  }
   179  
   180  func TestJSONMarshalURLAsMember(t *testing.T) {
   181  	type objectType struct {
   182  		URL URL `json:"url,omitempty"`
   183  	}
   184  
   185  	testCases := map[string]struct {
   186  		obj     *objectType
   187  		want    []byte
   188  		wantErr string
   189  	}{
   190  		"nil": {
   191  			want: []byte(`null`),
   192  		},
   193  		"empty": {
   194  			obj:  &objectType{},
   195  			want: []byte(`{"url":""}`),
   196  		},
   197  		"relative": {
   198  			obj:  &objectType{URL: URL{Path: "/path/to/something"}},
   199  			want: []byte(`{"url":"/path/to/something"}`),
   200  		},
   201  		"url": {
   202  			obj: &objectType{URL: URL{
   203  				Scheme: "http",
   204  				Host:   "path",
   205  				Path:   "/to/something",
   206  			}},
   207  			want: []byte(`{"url":"http://path/to/something"}`),
   208  		},
   209  		"empty url": {
   210  			obj:  &objectType{URL: URL{}},
   211  			want: []byte(`{"url":""}`),
   212  		},
   213  	}
   214  	for n, tc := range testCases {
   215  		t.Run(n, func(t *testing.T) {
   216  			got, err := json.Marshal(tc.obj)
   217  
   218  			if tc.wantErr != "" || err != nil {
   219  				var gotErr string
   220  				if err != nil {
   221  					gotErr = err.Error()
   222  				}
   223  				if diff := cmp.Diff(tc.wantErr, gotErr); diff != "" {
   224  					t.Error("unexpected error (-want, +got) =", diff)
   225  				}
   226  				return
   227  			}
   228  
   229  			if diff := cmp.Diff(tc.want, got); diff != "" {
   230  				t.Error("unexpected object (-want, +got) =", diff)
   231  				t.Log("got:", string(got))
   232  			}
   233  		})
   234  	}
   235  }
   236  
   237  type objectType struct {
   238  	URL *URL `json:"url,omitempty"`
   239  }
   240  
   241  func TestJSONMarshalURLAsPointerMember(t *testing.T) {
   242  	testCases := map[string]struct {
   243  		obj     *objectType
   244  		want    []byte
   245  		wantErr string
   246  	}{
   247  		"nil": {
   248  			want: []byte(`null`),
   249  		},
   250  		"empty": {
   251  			obj:  &objectType{},
   252  			want: []byte(`{}`),
   253  		},
   254  		"relative": {
   255  			obj:  &objectType{URL: &URL{Path: "/path/to/something"}},
   256  			want: []byte(`{"url":"/path/to/something"}`),
   257  		},
   258  		"url": {
   259  			obj: &objectType{URL: &URL{
   260  				Scheme: "http",
   261  				Host:   "path",
   262  				Path:   "/to/something",
   263  			}},
   264  			want: []byte(`{"url":"http://path/to/something"}`),
   265  		},
   266  		"empty url": {
   267  			obj:  &objectType{URL: &URL{}},
   268  			want: []byte(`{"url":""}`),
   269  		},
   270  	}
   271  	for n, tc := range testCases {
   272  		t.Run(n, func(t *testing.T) {
   273  			got, err := json.Marshal(tc.obj)
   274  
   275  			if tc.wantErr != "" || err != nil {
   276  				var gotErr string
   277  				if err != nil {
   278  					gotErr = err.Error()
   279  				}
   280  				if diff := cmp.Diff(tc.wantErr, gotErr); diff != "" {
   281  					t.Error("unexpected error (-want, +got) =", diff)
   282  				}
   283  				return
   284  			}
   285  
   286  			if diff := cmp.Diff(tc.want, got); diff != "" {
   287  				t.Error("unexpected object (-want, +got) =", diff)
   288  				t.Log("got:", string(got))
   289  			}
   290  		})
   291  	}
   292  }
   293  
   294  func TestJSONUnmarshalURLAsMember(t *testing.T) {
   295  	type objectType struct {
   296  		URL URL `json:"url,omitempty"`
   297  	}
   298  	testCases := map[string]struct {
   299  		b       []byte
   300  		want    *objectType
   301  		wantErr string
   302  	}{
   303  		"zero": {
   304  			wantErr: "unexpected end of JSON input",
   305  		},
   306  		"empty": {
   307  			b:    []byte(`{}`),
   308  			want: &objectType{},
   309  		},
   310  		"invalid format": {
   311  			b:       []byte(`{"url":"%"}`),
   312  			wantErr: `invalid URL escape "%"`,
   313  		},
   314  		"relative": {
   315  			b:    []byte(`{"url":"/path/to/something"}`),
   316  			want: &objectType{URL: URL{Path: "/path/to/something"}},
   317  		},
   318  		"url": {
   319  			b: []byte(`{"url":"http://path/to/something"}`),
   320  			want: &objectType{URL: URL{
   321  				Scheme: "http",
   322  				Host:   "path",
   323  				Path:   "/to/something",
   324  			}},
   325  		},
   326  		"empty url": {
   327  			b:    []byte(`{"url":""}`),
   328  			want: &objectType{URL: URL{}},
   329  		},
   330  	}
   331  	for n, tc := range testCases {
   332  		t.Run(n, func(t *testing.T) {
   333  			got := &objectType{}
   334  			err := json.Unmarshal(tc.b, got)
   335  
   336  			if tc.wantErr != "" || err != nil {
   337  				var gotErr string
   338  				if err != nil {
   339  					gotErr = err.Error()
   340  				}
   341  				if !strings.Contains(gotErr, tc.wantErr) {
   342  					t.Errorf("Error `%s` does not contain wanted string `%s`", gotErr, tc.wantErr)
   343  				}
   344  				return
   345  			}
   346  
   347  			if diff := cmp.Diff(tc.want, got); diff != "" {
   348  				t.Error("Unexpected object (-want, +got) =", diff)
   349  			}
   350  		})
   351  	}
   352  }
   353  
   354  func TestJSONUnmarshalURLAsMemberPointer(t *testing.T) {
   355  	type objectType struct {
   356  		URL *URL `json:"url,omitempty"`
   357  	}
   358  	testCases := []struct {
   359  		name    string
   360  		b       []byte
   361  		want    *objectType
   362  		wantErr string
   363  	}{
   364  		{
   365  			name:    "zero",
   366  			wantErr: "unexpected end of JSON input",
   367  		}, {
   368  			name: "empty",
   369  			b:    []byte(`{}`),
   370  			want: &objectType{},
   371  		}, {
   372  			name:    "invalid format",
   373  			b:       []byte(`{"url":"%"}`),
   374  			wantErr: `invalid URL escape "%"`,
   375  		}, {
   376  			name: "relative",
   377  			b:    []byte(`{"url":"/path/to/something"}`),
   378  			want: &objectType{URL: &URL{Path: "/path/to/something"}},
   379  		}, {
   380  			name: "url",
   381  			b:    []byte(`{"url":"http://path/to/something"}`),
   382  			want: &objectType{URL: &URL{
   383  				Scheme: "http",
   384  				Host:   "path",
   385  				Path:   "/to/something",
   386  			}},
   387  		}, {
   388  			name: "empty url",
   389  			b:    []byte(`{"url":""}`),
   390  			want: &objectType{URL: &URL{}},
   391  		},
   392  	}
   393  	for _, tc := range testCases {
   394  		t.Run(tc.name, func(t *testing.T) {
   395  			got := &objectType{}
   396  			err := json.Unmarshal(tc.b, got)
   397  
   398  			if tc.wantErr != "" || err != nil {
   399  				var gotErr string
   400  				if err != nil {
   401  					gotErr = err.Error()
   402  				}
   403  				if !strings.Contains(gotErr, tc.wantErr) {
   404  					t.Errorf("Error `%s` does not contain wanted string `%s`", gotErr, tc.wantErr)
   405  				}
   406  				return
   407  			}
   408  
   409  			if diff := cmp.Diff(tc.want, got); diff != "" {
   410  				t.Error("Unexpected object (-want, +got) =", diff)
   411  			}
   412  		})
   413  	}
   414  }
   415  
   416  func TestURLString(t *testing.T) {
   417  	testCases := map[string]struct {
   418  		t    *URL
   419  		want string
   420  	}{
   421  		"nil": {},
   422  		"empty": {
   423  			t:    &URL{},
   424  			want: "",
   425  		},
   426  		"relative": {
   427  			t:    &URL{Path: "/path/to/something"},
   428  			want: "/path/to/something",
   429  		},
   430  		"nopath": {
   431  			t:    HTTPS("foo"),
   432  			want: "https://foo",
   433  		},
   434  		"absolute": {
   435  			t: &URL{
   436  				Scheme: "http",
   437  				Host:   "path",
   438  				Path:   "/to/something",
   439  			},
   440  			want: "http://path/to/something",
   441  		},
   442  	}
   443  	for n, tc := range testCases {
   444  		t.Run(n, func(t *testing.T) {
   445  			got := tc.t
   446  
   447  			if diff := cmp.Diff(tc.want, got.String()); diff != "" {
   448  				t.Error("unexpected string (-want, +got) =", diff)
   449  			}
   450  			if diff := cmp.Diff(tc.want, got.URL().String()); diff != "" {
   451  				t.Error("unexpected URL (-want, +got) =", diff)
   452  			}
   453  		})
   454  	}
   455  }
   456  
   457  // These are lifted from the net/url url_test.go
   458  var resolveReferenceTests = []struct {
   459  	base, rel, expected string
   460  }{
   461  	// Absolute URL references
   462  	{"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"},
   463  	{"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"},
   464  	{"http://foo.com/", "https://bar.com/?", "https://bar.com/?"},
   465  	{"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"},
   466  
   467  	// Path-absolute references
   468  	{"http://foo.com/bar", "/baz", "http://foo.com/baz"},
   469  	{"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"},
   470  	{"http://foo.com/bar?a=b", "/baz?", "http://foo.com/baz?"},
   471  	{"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"},
   472  
   473  	// Multiple slashes
   474  	{"http://foo.com/bar", "http://foo.com//baz", "http://foo.com//baz"},
   475  	{"http://foo.com/bar", "http://foo.com///baz/quux", "http://foo.com///baz/quux"},
   476  
   477  	// Scheme-relative
   478  	{"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"},
   479  
   480  	// Path-relative references:
   481  
   482  	// ... current directory
   483  	{"http://foo.com", ".", "http://foo.com/"},
   484  	{"http://foo.com/bar", ".", "http://foo.com/"},
   485  	{"http://foo.com/bar/", ".", "http://foo.com/bar/"},
   486  
   487  	// ... going down
   488  	{"http://foo.com", "bar", "http://foo.com/bar"},
   489  	{"http://foo.com/", "bar", "http://foo.com/bar"},
   490  	{"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"},
   491  
   492  	// ... going up
   493  	{"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"},
   494  	{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
   495  	{"http://foo.com/bar", "..", "http://foo.com/"},
   496  	{"http://foo.com/bar/baz", "./..", "http://foo.com/"},
   497  	// ".." in the middle (issue 3560)
   498  	{"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"},
   499  	{"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"},
   500  	{"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"},
   501  	{"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"},
   502  	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"},
   503  	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"},
   504  	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
   505  	{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"},
   506  
   507  	// Remove any dot-segments prior to forming the target URI.
   508  	// http://tools.ietf.org/html/rfc3986#section-5.2.4
   509  	{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"},
   510  
   511  	// Triple dot isn't special
   512  	{"http://foo.com/bar", "...", "http://foo.com/..."},
   513  
   514  	// Fragment
   515  	{"http://foo.com/bar", ".#frag", "http://foo.com/#frag"},
   516  	{"http://example.org/", "#!$&%27()*+,;=", "http://example.org/#!$&%27()*+,;="},
   517  
   518  	// Paths with escaping (issue 16947).
   519  	{"http://foo.com/foo%2fbar/", "../baz", "http://foo.com/baz"},
   520  	{"http://foo.com/1/2%2f/3%2f4/5", "../../a/b/c", "http://foo.com/1/a/b/c"},
   521  	{"http://foo.com/1/2/3", "./a%2f../../b/..%2fc", "http://foo.com/1/2/b/..%2fc"},
   522  	{"http://foo.com/1/2%2f/3%2f4/5", "./a%2f../b/../c", "http://foo.com/1/2%2f/3%2f4/a%2f../c"},
   523  	{"http://foo.com/foo%20bar/", "../baz", "http://foo.com/baz"},
   524  	{"http://foo.com/foo", "../bar%2fbaz", "http://foo.com/bar%2fbaz"},
   525  	{"http://foo.com/foo%2dbar/", "./baz-quux", "http://foo.com/foo%2dbar/baz-quux"},
   526  
   527  	// RFC 3986: Normal Examples
   528  	// http://tools.ietf.org/html/rfc3986#section-5.4.1
   529  	{"http://a/b/c/d;p?q", "g:h", "g:h"},
   530  	{"http://a/b/c/d;p?q", "g", "http://a/b/c/g"},
   531  	{"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"},
   532  	{"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"},
   533  	{"http://a/b/c/d;p?q", "/g", "http://a/g"},
   534  	{"http://a/b/c/d;p?q", "//g", "http://g"},
   535  	{"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"},
   536  	{"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"},
   537  	{"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"},
   538  	{"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"},
   539  	{"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"},
   540  	{"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"},
   541  	{"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"},
   542  	{"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"},
   543  	{"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"},
   544  	{"http://a/b/c/d;p?q", ".", "http://a/b/c/"},
   545  	{"http://a/b/c/d;p?q", "./", "http://a/b/c/"},
   546  	{"http://a/b/c/d;p?q", "..", "http://a/b/"},
   547  	{"http://a/b/c/d;p?q", "../", "http://a/b/"},
   548  	{"http://a/b/c/d;p?q", "../g", "http://a/b/g"},
   549  	{"http://a/b/c/d;p?q", "../..", "http://a/"},
   550  	{"http://a/b/c/d;p?q", "../../", "http://a/"},
   551  	{"http://a/b/c/d;p?q", "../../g", "http://a/g"},
   552  
   553  	// RFC 3986: Abnormal Examples
   554  	// http://tools.ietf.org/html/rfc3986#section-5.4.2
   555  	{"http://a/b/c/d;p?q", "../../../g", "http://a/g"},
   556  	{"http://a/b/c/d;p?q", "../../../../g", "http://a/g"},
   557  	{"http://a/b/c/d;p?q", "/./g", "http://a/g"},
   558  	{"http://a/b/c/d;p?q", "/../g", "http://a/g"},
   559  	{"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."},
   560  	{"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"},
   561  	{"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."},
   562  	{"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"},
   563  	{"http://a/b/c/d;p?q", "./../g", "http://a/b/g"},
   564  	{"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"},
   565  	{"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"},
   566  	{"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"},
   567  	{"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"},
   568  	{"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"},
   569  	{"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"},
   570  	{"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"},
   571  	{"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"},
   572  	{"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"},
   573  
   574  	// Extras.
   575  	{"https://a/b/c/d;p?q", "//g?q", "https://g?q"},
   576  	{"https://a/b/c/d;p?q", "//g#s", "https://g#s"},
   577  	{"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"},
   578  	{"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"},
   579  	{"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"},
   580  }
   581  
   582  func TestResolveReference(t *testing.T) {
   583  	mustParse := func(url string) *URL {
   584  		u, err := ParseURL(url)
   585  		if err != nil {
   586  			t.Fatalf("ParseURL(%q) got err %v", url, err)
   587  		}
   588  		return u
   589  	}
   590  
   591  	for _, tc := range resolveReferenceTests {
   592  		apisURL := mustParse(tc.base)
   593  		apisRel := mustParse(tc.rel)
   594  		if got := apisURL.ResolveReference(apisRel).String(); got != tc.expected {
   595  			t.Errorf("URL(%q).ResolveReference(%q)\ngot  %q\nwant %q", tc.base, tc.rel, got, tc.expected)
   596  		}
   597  	}
   598  }
   599  
   600  func TestSemanticEquality(t *testing.T) {
   601  	u1, err := ParseURL("https://user:password@example.com")
   602  	if err != nil {
   603  		t.Fatal("ParseURL() got err", err)
   604  	}
   605  
   606  	u2, err := ParseURL("https://user:password@example.com")
   607  	if err != nil {
   608  		t.Fatal("ParseURL() got err", err)
   609  	}
   610  
   611  	u3, err := ParseURL("https://another-user:password@example.com")
   612  	if err != nil {
   613  		t.Fatal("ParseURL() got err", err)
   614  	}
   615  
   616  	if !equality.Semantic.DeepEqual(u1, u2) {
   617  		t.Errorf("expected urls to be equivalent")
   618  	}
   619  
   620  	if equality.Semantic.DeepEqual(u1, u3) {
   621  		t.Errorf("expected urls to be different")
   622  	}
   623  }