<|start_filename|>http_client_test.go<|end_filename|>
package requests_test

import (
	"net/http"
	"net/http/httptest"

	"github.com/albertoleal/requests"
	. "gopkg.in/check.v1"
)

func (s *S) TestMakeRequest(c *C) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"name": "Alice"}`))
	}))
	defer server.Close()
	httpClient := requests.NewHTTPClient(server.URL)

	args := requests.Args{Method: "GET", Path: "/path", Body: nil, AcceptableCode: http.StatusOK}
	_, _, body, err := httpClient.MakeRequest(args)
	c.Assert(string(body), Equals, `{"name": "Alice"}`)
	c.Check(err, IsNil)
}

func (s *S) TestMakeRequestWithNonAcceptableCode(c *C) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"name": "Alice"}`))
	}))
	defer server.Close()
	httpClient := requests.NewHTTPClient(server.URL)

	args := requests.Args{Method: "GET", Path: "/path", Body: nil, AcceptableCode: http.StatusBadRequest}
	_, _, body, err := httpClient.MakeRequest(args)
	c.Assert(string(body), Equals, `{"name": "Alice"}`)
	e, ok := err.(requests.ResponseError)
	c.Assert(ok, Equals, true)
	c.Assert(e.Error(), Equals, "The response was invalid or cannot be served.")
}

func (s *S) TestReturnsErrorWhenHostIsInvalid(c *C) {
	httpClient := requests.NewHTTPClient("://invalid-host")
	args := requests.Args{Method: "GET", Path: "/path", Body: nil}
	_, _, _, err := httpClient.MakeRequest(args)
	_, ok := err.(requests.InvalidHostError)
	c.Assert(ok, Equals, true)
}

func (s *S) TestReturnsErrorWhenRequestIsInvalid(c *C) {
	httpClient := requests.NewHTTPClient("invalid-host")
	args := requests.Args{Method: "GET", Path: "/path", Body: nil}
	_, _, _, err := httpClient.MakeRequest(args)
	_, ok := err.(requests.RequestError)
	c.Assert(ok, Equals, true)
}

func (s *S) TestReturnsErrorWhenResponseIsInvalid(c *C) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Length", "1")
		w.Write([]byte("{}"))
	}))
	defer server.Close()

	httpClient := requests.NewHTTPClient(server.URL)

	args := requests.Args{Method: "GET", Path: "/path", Body: nil}
	_, _, _, err := httpClient.MakeRequest(args)
	_, ok := err.(requests.ResponseError)
	c.Assert(ok, Equals, true)
}

func (s *S) TestIncludesHeader(c *C) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		auth := req.Header.Get("Authorization")
		c.Assert(auth, Equals, "Token abcde")
	}))
	defer server.Close()

	httpClient := requests.NewHTTPClient(server.URL)

	args := requests.Args{Method: "GET", Path: "/path", Body: nil, Headers: http.Header{"Authorization": {"Token abcde"}}}
	httpClient.MakeRequest(args)
}

func (s *S) TestReturnsDefaultError(c *C) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	httpClient := requests.NewHTTPClient(server.URL)

	args := requests.Args{Method: "GET", Path: "/path", Body: nil}
	_, _, _, err := httpClient.MakeRequest(args)
	e, ok := err.(requests.ResponseError)
	c.Assert(e.Error(), Equals, "The response was invalid or cannot be served.")
	c.Assert(ok, Equals, true)
}


<|start_filename|>http_client.go<|end_filename|>
package requests

import (
	"crypto/tls"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type HTTPClient struct {
	Host   string
	client *http.Client
}

func NewHTTPClient(host string) HTTPClient {
	return HTTPClient{
		Host: host,
		client: &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					InsecureSkipVerify: true,
				},
			},
		},
	}
}

type Args struct {
	AcceptableCode int
	Body           interface{}
	Path           string
	Method         string
	Headers        http.Header
	Username       string
	Password       string
}

func (c *HTTPClient) MakeRequest(args Args) (http.Header, int, []byte, error) {
	header := make(map[string][]string)

	url, err := url.Parse(c.Host)
	if err != nil {
		return header, 0, []byte{}, NewInvalidHostError(err)
	}
	url.Path = args.Path

	body, ok := args.Body.(string)
	if !ok {
		body = ""
	}

	req, err := http.NewRequest(args.Method, url.String(), strings.NewReader(body))
	if err != nil {
		return header, 0, []byte{}, NewRequestError(err)
	}

	if args.Headers != nil {
		req.Header = args.Headers
	}
	if args.Username != "" && args.Password != "" {
		req.SetBasicAuth(args.Username, args.Password)
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return header, 0, []byte{}, NewRequestError(err)
	}
	respBody, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return resp.Header, resp.StatusCode, []byte{}, NewResponseError(err)
	}

	if resp.StatusCode == args.AcceptableCode {
		return resp.Header, resp.StatusCode, respBody, nil
	}

	return resp.Header, resp.StatusCode, respBody, NewResponseError(ErrBadResponse)
}


<|start_filename|>errors.go<|end_filename|>
// Package errors contains common business errors to be user all over the server.
package requests

import "errors"

var (
	ErrBadRequest  = errors.New("The request was invalid or cannot be served.")
	ErrBadResponse = errors.New("The response was invalid or cannot be served.")
)

type InvalidHostError struct {
	description error
}

func NewInvalidHostError(err error) InvalidHostError {
	return InvalidHostError{description: err}
}

func (err InvalidHostError) Error() string {
	return err.description.Error()
}

type RequestError struct {
	description error
}

func NewRequestError(err error) RequestError {
	return RequestError{description: err}
}

func (err RequestError) Error() string {
	return err.description.Error()
}

type ResponseError struct {
	description error
}

func NewResponseError(err error) ResponseError {
	return ResponseError{description: err}
}

func (err ResponseError) Error() string {
	return err.description.Error()
}
