Skip to content

Commit 431bfea

Browse files
committed
Split the library into packages
1 parent 951b7cc commit 431bfea

16 files changed

+1332
-509
lines changed

api/access_token.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package api
2+
3+
// AccessToken is an OAuth access token.
4+
type AccessToken struct {
5+
// The token value, typically a 40-character random string.
6+
Token string
7+
// The token type, e.g. "bearer".
8+
Type string
9+
// Space-separated list of OAuth scopes that this token grants.
10+
Scope string
11+
}
12+
13+
func (f FormResponse) AccessToken() (*AccessToken, error) {
14+
if accessToken := f.Get("access_token"); accessToken != "" {
15+
return &AccessToken{
16+
Token: accessToken,
17+
Type: f.Get("token_type"),
18+
Scope: f.Get("scope"),
19+
}, nil
20+
}
21+
22+
return nil, f.Err()
23+
}

api/access_token_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package api
2+
3+
import (
4+
"net/url"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
func TestFormResponse_AccessToken(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
response FormResponse
13+
want *AccessToken
14+
wantErr *Error
15+
}{
16+
{
17+
name: "with token",
18+
response: FormResponse{
19+
values: url.Values{
20+
"access_token": []string{"ATOKEN"},
21+
"token_type": []string{"bearer"},
22+
"scope": []string{"repo gist"},
23+
},
24+
},
25+
want: &AccessToken{
26+
Token: "ATOKEN",
27+
Type: "bearer",
28+
Scope: "repo gist",
29+
},
30+
wantErr: nil,
31+
},
32+
{
33+
name: "no token",
34+
response: FormResponse{
35+
StatusCode: 200,
36+
values: url.Values{
37+
"error": []string{"access_denied"},
38+
},
39+
},
40+
want: nil,
41+
wantErr: &Error{
42+
Code: "access_denied",
43+
ResponseCode: 200,
44+
},
45+
},
46+
}
47+
for _, tt := range tests {
48+
t.Run(tt.name, func(t *testing.T) {
49+
got, err := tt.response.AccessToken()
50+
if err != nil {
51+
apiError := err.(*Error)
52+
if !reflect.DeepEqual(apiError, tt.wantErr) {
53+
t.Fatalf("error %v, want %v", apiError, tt.wantErr)
54+
}
55+
} else if tt.wantErr != nil {
56+
t.Fatalf("want error %v, got nil", tt.wantErr)
57+
}
58+
if !reflect.DeepEqual(got, tt.want) {
59+
t.Errorf("FormResponse.AccessToken() = %v, want %v", got, tt.want)
60+
}
61+
})
62+
}
63+
}

api/form.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package api
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"io/ioutil"
7+
"net/http"
8+
"net/url"
9+
"strings"
10+
)
11+
12+
type httpClient interface {
13+
PostForm(string, url.Values) (*http.Response, error)
14+
}
15+
16+
// FormResponse is the parsed "www-form-urlencoded" response from the server.
17+
type FormResponse struct {
18+
StatusCode int
19+
20+
requestURI string
21+
values url.Values
22+
}
23+
24+
// Get the response value named k.
25+
func (f FormResponse) Get(k string) string {
26+
return f.values.Get(k)
27+
}
28+
29+
func (f FormResponse) Err() error {
30+
return &Error{
31+
RequestURI: f.requestURI,
32+
ResponseCode: f.StatusCode,
33+
Code: f.Get("error"),
34+
message: f.Get("error_description"),
35+
}
36+
}
37+
38+
// Error is the result of an unexpected HTTP response from the server.
39+
type Error struct {
40+
Code string
41+
ResponseCode int
42+
RequestURI string
43+
44+
message string
45+
}
46+
47+
func (e Error) Error() string {
48+
if e.message != "" {
49+
return fmt.Sprintf("%s (%s)", e.message, e.Code)
50+
}
51+
if e.Code != "" {
52+
return e.Code
53+
}
54+
return fmt.Sprintf("HTTP %d", e.ResponseCode)
55+
}
56+
57+
// PostForm makes an POST request by serializing input parameters as a form and parsing the response
58+
// of the same type.
59+
func PostForm(c httpClient, u string, params url.Values) (*FormResponse, error) {
60+
resp, err := c.PostForm(u, params)
61+
if err != nil {
62+
return nil, err
63+
}
64+
defer resp.Body.Close()
65+
66+
r := &FormResponse{
67+
StatusCode: resp.StatusCode,
68+
requestURI: u,
69+
}
70+
71+
if contentType(resp.Header.Get("Content-Type")) == formType {
72+
var bb []byte
73+
bb, err = ioutil.ReadAll(resp.Body)
74+
if err != nil {
75+
return r, err
76+
}
77+
78+
r.values, err = url.ParseQuery(string(bb))
79+
if err != nil {
80+
return r, err
81+
}
82+
} else {
83+
_, err = io.Copy(ioutil.Discard, resp.Body)
84+
if err != nil {
85+
return r, err
86+
}
87+
}
88+
89+
return r, nil
90+
}
91+
92+
const formType = "application/x-www-form-urlencoded"
93+
94+
func contentType(t string) string {
95+
if i := strings.IndexRune(t, ';'); i >= 0 {
96+
return t[0:i]
97+
}
98+
return t
99+
}

api/form_test.go

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package api
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"net/http"
7+
"net/url"
8+
"reflect"
9+
"testing"
10+
)
11+
12+
func TestFormResponse_Get(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
response FormResponse
16+
key string
17+
want string
18+
}{
19+
{
20+
name: "blank",
21+
response: FormResponse{},
22+
key: "access_token",
23+
want: "",
24+
},
25+
{
26+
name: "with value",
27+
response: FormResponse{
28+
values: url.Values{
29+
"access_token": []string{"ATOKEN"},
30+
},
31+
},
32+
key: "access_token",
33+
want: "ATOKEN",
34+
},
35+
}
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
if got := tt.response.Get(tt.key); got != tt.want {
39+
t.Errorf("FormResponse.Get() = %v, want %v", got, tt.want)
40+
}
41+
})
42+
}
43+
}
44+
45+
func TestFormResponse_Err(t *testing.T) {
46+
tests := []struct {
47+
name string
48+
response FormResponse
49+
wantErr Error
50+
errorMsg string
51+
}{
52+
{
53+
name: "blank",
54+
response: FormResponse{},
55+
wantErr: Error{},
56+
errorMsg: "HTTP 0",
57+
},
58+
{
59+
name: "with values",
60+
response: FormResponse{
61+
StatusCode: 422,
62+
requestURI: "http://example.com/path",
63+
values: url.Values{
64+
"error": []string{"try_again"},
65+
"error_description": []string{"maybe it works later"},
66+
},
67+
},
68+
wantErr: Error{
69+
Code: "try_again",
70+
ResponseCode: 422,
71+
RequestURI: "http://example.com/path",
72+
},
73+
errorMsg: "maybe it works later (try_again)",
74+
},
75+
{
76+
name: "no values",
77+
response: FormResponse{
78+
StatusCode: 422,
79+
requestURI: "http://example.com/path",
80+
},
81+
wantErr: Error{
82+
Code: "",
83+
ResponseCode: 422,
84+
RequestURI: "http://example.com/path",
85+
},
86+
errorMsg: "HTTP 422",
87+
},
88+
}
89+
for _, tt := range tests {
90+
t.Run(tt.name, func(t *testing.T) {
91+
err := tt.response.Err()
92+
if err == nil {
93+
t.Fatalf("FormResponse.Err() = %v, want %v", nil, tt.wantErr)
94+
}
95+
apiError := err.(*Error)
96+
if apiError.Code != tt.wantErr.Code {
97+
t.Errorf("Error.Code = %v, want %v", apiError.Code, tt.wantErr.Code)
98+
}
99+
if apiError.ResponseCode != tt.wantErr.ResponseCode {
100+
t.Errorf("Error.ResponseCode = %v, want %v", apiError.ResponseCode, tt.wantErr.ResponseCode)
101+
}
102+
if apiError.RequestURI != tt.wantErr.RequestURI {
103+
t.Errorf("Error.RequestURI = %v, want %v", apiError.RequestURI, tt.wantErr.RequestURI)
104+
}
105+
if apiError.Error() != tt.errorMsg {
106+
t.Errorf("Error.Error() = %q, want %q", apiError.Error(), tt.errorMsg)
107+
}
108+
})
109+
}
110+
}
111+
112+
type apiClient struct {
113+
status int
114+
body string
115+
contentType string
116+
117+
postCount int
118+
}
119+
120+
func (c *apiClient) PostForm(u string, params url.Values) (*http.Response, error) {
121+
c.postCount++
122+
return &http.Response{
123+
Body: ioutil.NopCloser(bytes.NewBufferString(c.body)),
124+
Header: http.Header{
125+
"Content-Type": {c.contentType},
126+
},
127+
StatusCode: c.status,
128+
}, nil
129+
}
130+
131+
func TestPostForm(t *testing.T) {
132+
type args struct {
133+
url string
134+
params url.Values
135+
}
136+
tests := []struct {
137+
name string
138+
args args
139+
http apiClient
140+
want *FormResponse
141+
wantErr bool
142+
}{
143+
{
144+
name: "success",
145+
args: args{
146+
url: "https://github.com/oauth",
147+
},
148+
http: apiClient{
149+
body: "access_token=123abc&scopes=repo%20gist",
150+
status: 200,
151+
contentType: "application/x-www-form-urlencoded; charset=utf-8",
152+
},
153+
want: &FormResponse{
154+
StatusCode: 200,
155+
requestURI: "https://github.com/oauth",
156+
values: url.Values{
157+
"access_token": {"123abc"},
158+
"scopes": {"repo gist"},
159+
},
160+
},
161+
wantErr: false,
162+
},
163+
{
164+
name: "HTML response",
165+
args: args{
166+
url: "https://github.com/oauth",
167+
},
168+
http: apiClient{
169+
body: "<h1>Something went wrong</h1>",
170+
status: 502,
171+
contentType: "text/html",
172+
},
173+
want: &FormResponse{
174+
StatusCode: 502,
175+
requestURI: "https://github.com/oauth",
176+
values: url.Values(nil),
177+
},
178+
wantErr: false,
179+
},
180+
}
181+
for _, tt := range tests {
182+
t.Run(tt.name, func(t *testing.T) {
183+
got, err := PostForm(&tt.http, tt.args.url, tt.args.params)
184+
if (err != nil) != tt.wantErr {
185+
t.Errorf("PostForm() error = %v, wantErr %v", err, tt.wantErr)
186+
return
187+
}
188+
if tt.http.postCount != 1 {
189+
t.Errorf("expected PostForm to happen 1 time; happened %d times", tt.http.postCount)
190+
}
191+
if !reflect.DeepEqual(got, tt.want) {
192+
t.Errorf("PostForm() = %v, want %v", got, tt.want)
193+
}
194+
})
195+
}
196+
}

0 commit comments

Comments
 (0)