Skip to content

Commit fdc5581

Browse files
author
Adam Hughes
committed
Fix BaseURL path handling (#30)
1 parent bd4e509 commit fdc5581

File tree

2 files changed

+197
-2
lines changed

2 files changed

+197
-2
lines changed

client/client.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"net/http"
1212
"net/url"
13+
"strings"
1314

1415
"github.com/go-log/log"
1516
)
@@ -66,6 +67,13 @@ func New(cfg *Config) (c *Client, err error) {
6667
return nil, fmt.Errorf("unsupported protocol scheme %q", baseURL.Scheme)
6768
}
6869

70+
// If baseURL has a path component, ensure it is terminated with a separator, to prevent
71+
// url.ResolveReference from stripping the final component of the path when constructing
72+
// request URL.
73+
if p := baseURL.Path; p != "" && !strings.HasSuffix(p, "/") {
74+
baseURL.Path += "/"
75+
}
76+
6977
c = &Client{
7078
BaseURL: baseURL,
7179
AuthToken: cfg.AuthToken,
@@ -87,10 +95,10 @@ func New(cfg *Config) (c *Client, err error) {
8795
return c, nil
8896
}
8997

90-
// newRequest returns a new Request given a method, path, query, and optional body.
98+
// newRequest returns a new Request given a method, relative path, query, and optional body.
9199
func (c *Client) newRequest(method, path string, body io.Reader) (r *http.Request, err error) {
92100
u := c.BaseURL.ResolveReference(&url.URL{
93-
Path: path,
101+
Path: strings.TrimPrefix(path, "/"), // trim leading separator as path is relative.
94102
})
95103

96104
r, err = http.NewRequest(method, u.String(), body)

client/client_test.go

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright (c) 2018-2020, Sylabs Inc. All rights reserved.
2+
// This software is licensed under a 3-clause BSD license. Please consult the
3+
// LICENSE.md file distributed with the sources of this project regarding your
4+
// rights to use or distribute this software.
5+
6+
package client
7+
8+
import (
9+
"io/ioutil"
10+
"net/http"
11+
"strings"
12+
"testing"
13+
14+
"github.com/go-log/log"
15+
)
16+
17+
type mockLogger struct{}
18+
19+
func (*mockLogger) Log(v ...interface{}) {}
20+
func (*mockLogger) Logf(format string, v ...interface{}) {}
21+
22+
func TestNewClient(t *testing.T) {
23+
httpClient := &http.Client{}
24+
logger := &mockLogger{}
25+
26+
tests := []struct {
27+
name string
28+
cfg *Config
29+
wantErr bool
30+
wantURL string
31+
wantAuthToken string
32+
wantUserAgent string
33+
wantHTTPClient *http.Client
34+
wantLogger log.Logger
35+
}{
36+
{"NilConfig", nil, false, defaultBaseURL, "", "", http.DefaultClient, log.DefaultLogger},
37+
{"HTTPBaseURL", &Config{
38+
BaseURL: "http://build.staging.sylabs.io",
39+
}, false, "http://build.staging.sylabs.io", "", "", http.DefaultClient, log.DefaultLogger},
40+
{"HTTPSBaseURL", &Config{
41+
BaseURL: "https://build.staging.sylabs.io",
42+
}, false, "https://build.staging.sylabs.io", "", "", http.DefaultClient, log.DefaultLogger},
43+
{"HTTPSBaseURLWithPath", &Config{
44+
BaseURL: "https://build.staging.sylabs.io/path",
45+
}, false, "https://build.staging.sylabs.io/path/", "", "", http.DefaultClient, log.DefaultLogger},
46+
{"HTTPSBaseURLWithPathSlash", &Config{
47+
BaseURL: "https://build.staging.sylabs.io/path/",
48+
}, false, "https://build.staging.sylabs.io/path/", "", "", http.DefaultClient, log.DefaultLogger},
49+
{"UnsupportedBaseURL", &Config{
50+
BaseURL: "bad:",
51+
}, true, "", "", "", nil, log.DefaultLogger},
52+
{"BadBaseURL", &Config{
53+
BaseURL: ":",
54+
}, true, "", "", "", nil, log.DefaultLogger},
55+
{"AuthToken", &Config{
56+
AuthToken: "blah",
57+
}, false, defaultBaseURL, "blah", "", http.DefaultClient, log.DefaultLogger},
58+
{"UserAgent", &Config{
59+
UserAgent: "Secret Agent Man",
60+
}, false, defaultBaseURL, "", "Secret Agent Man", http.DefaultClient, log.DefaultLogger},
61+
{"HTTPClient", &Config{
62+
HTTPClient: httpClient,
63+
}, false, defaultBaseURL, "", "", httpClient, log.DefaultLogger},
64+
{"Logger", &Config{
65+
Logger: logger,
66+
}, false, defaultBaseURL, "", "", http.DefaultClient, logger},
67+
}
68+
69+
for _, tt := range tests {
70+
t.Run(tt.name, func(t *testing.T) {
71+
c, err := New(tt.cfg)
72+
if (err != nil) != tt.wantErr {
73+
t.Fatalf("got err %v, want %v", err, tt.wantErr)
74+
}
75+
76+
if err == nil {
77+
if got, want := c.BaseURL.String(), tt.wantURL; got != want {
78+
t.Errorf("got host %v, want %v", got, want)
79+
}
80+
81+
if got, want := c.AuthToken, tt.wantAuthToken; got != want {
82+
t.Errorf("got auth token %v, want %v", got, want)
83+
}
84+
85+
if got, want := c.UserAgent, tt.wantUserAgent; got != want {
86+
t.Errorf("got user agent %v, want %v", got, want)
87+
}
88+
89+
if got, want := c.HTTPClient, tt.wantHTTPClient; got != want {
90+
t.Errorf("got HTTP client %v, want %v", got, want)
91+
}
92+
}
93+
})
94+
}
95+
}
96+
97+
func TestNewRequest(t *testing.T) {
98+
tests := []struct {
99+
name string
100+
cfg *Config
101+
method string
102+
path string
103+
body string
104+
wantErr bool
105+
wantURL string
106+
wantAuthBearer string
107+
wantUserAgent string
108+
}{
109+
{"BadMethod", nil, "b@d ", "", "", true, "", "", ""},
110+
{"NilConfigGet", nil, http.MethodGet, "/path", "", false, "https://build.sylabs.io/path", "", ""},
111+
{"NilConfigPost", nil, http.MethodPost, "/path", "", false, "https://build.sylabs.io/path", "", ""},
112+
{"NilConfigPostBody", nil, http.MethodPost, "/path", "body", false, "https://build.sylabs.io/path", "", ""},
113+
{"HTTPBaseURL", &Config{
114+
BaseURL: "http://build.staging.sylabs.io",
115+
}, http.MethodGet, "/path", "", false, "http://build.staging.sylabs.io/path", "", ""},
116+
{"HTTPSBaseURL", &Config{
117+
BaseURL: "https://build.staging.sylabs.io",
118+
}, http.MethodGet, "/path", "", false, "https://build.staging.sylabs.io/path", "", ""},
119+
{"BaseURLWithPath", &Config{
120+
BaseURL: "https://build.staging.sylabs.io/path",
121+
}, http.MethodGet, "/path", "", false, "https://build.staging.sylabs.io/path/path", "", ""},
122+
{"AuthToken", &Config{
123+
AuthToken: "blah",
124+
}, http.MethodGet, "/path", "", false, "https://build.sylabs.io/path", "BEARER blah", ""},
125+
{"UserAgent", &Config{
126+
UserAgent: "Secret Agent Man",
127+
}, http.MethodGet, "/path", "", false, "https://build.sylabs.io/path", "", "Secret Agent Man"},
128+
}
129+
130+
for _, tt := range tests {
131+
t.Run(tt.name, func(t *testing.T) {
132+
c, err := New(tt.cfg)
133+
if err != nil {
134+
t.Fatalf("failed to create client: %v", err)
135+
}
136+
137+
r, err := c.newRequest(tt.method, tt.path, strings.NewReader(tt.body))
138+
if (err != nil) != tt.wantErr {
139+
t.Fatalf("got err %v, wantErr %v", err, tt.wantErr)
140+
}
141+
142+
if err == nil {
143+
if got, want := r.Method, tt.method; got != want {
144+
t.Errorf("got method %v, want %v", got, want)
145+
}
146+
147+
if got, want := r.URL.String(), tt.wantURL; got != want {
148+
t.Errorf("got URL %v, want %v", got, want)
149+
}
150+
151+
b, err := ioutil.ReadAll(r.Body)
152+
if err != nil {
153+
t.Errorf("failed to read body: %v", err)
154+
}
155+
if got, want := string(b), tt.body; got != want {
156+
t.Errorf("got body %v, want %v", got, want)
157+
}
158+
159+
authBearer, ok := r.Header["Authorization"]
160+
if got, want := ok, (tt.wantAuthBearer != ""); got != want {
161+
t.Fatalf("presence of auth bearer %v, want %v", got, want)
162+
}
163+
if ok {
164+
if got, want := len(authBearer), 1; got != want {
165+
t.Fatalf("got %v auth bearer(s), want %v", got, want)
166+
}
167+
if got, want := authBearer[0], tt.wantAuthBearer; got != want {
168+
t.Errorf("got auth bearer %v, want %v", got, want)
169+
}
170+
}
171+
172+
userAgent, ok := r.Header["User-Agent"]
173+
if got, want := ok, (tt.wantUserAgent != ""); got != want {
174+
t.Fatalf("presence of user agent %v, want %v", got, want)
175+
}
176+
if ok {
177+
if got, want := len(userAgent), 1; got != want {
178+
t.Fatalf("got %v user agent(s), want %v", got, want)
179+
}
180+
if got, want := userAgent[0], tt.wantUserAgent; got != want {
181+
t.Errorf("got user agent %v, want %v", got, want)
182+
}
183+
}
184+
}
185+
})
186+
}
187+
}

0 commit comments

Comments
 (0)