refactor: internal package
This commit is contained in:
19
internal/httpx/httpx.go
Normal file
19
internal/httpx/httpx.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Package httpx provides helper functions for making HTTP requests.
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var client = Client(&http.Client{Timeout: 5 * time.Second})
|
||||
|
||||
// Client is something that can send HTTP requests.
|
||||
type Client interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Do sends an HTTP request and returns an HTTP response.
|
||||
func Do(req *http.Request) (*http.Response, error) {
|
||||
return client.Do(req)
|
||||
}
|
||||
40
internal/httpx/httpx_test.go
Normal file
40
internal/httpx/httpx_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDo(t *testing.T) {
|
||||
srv := MockServer()
|
||||
defer srv.Close()
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
uri := srv.URL + "/example.json"
|
||||
req, _ := http.NewRequest(http.MethodGet, uri, nil)
|
||||
|
||||
resp, err := Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("Do: unexpected error %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Do: expected status=%d, got %v", http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
})
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
uri := srv.URL + "/not-found.json"
|
||||
req, _ := http.NewRequest(http.MethodGet, uri, nil)
|
||||
|
||||
resp, err := Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("Do: unexpected error %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("Do: expected status=%d, got %v", http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
96
internal/httpx/mock.go
Normal file
96
internal/httpx/mock.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var contentTypes = map[string]string{
|
||||
".json": "application/json",
|
||||
".txt": "text/plain",
|
||||
}
|
||||
|
||||
// MockClient serves responses from the file system instead of remote calls.
|
||||
// Should be used for testing purposes only.
|
||||
type MockClient struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// Mock creates a new MockClient and installs it instead of the default one.
|
||||
func Mock(path ...string) *MockClient {
|
||||
dir := filepath.Join("testdata", filepath.Join(path...))
|
||||
c := &MockClient{dir: dir}
|
||||
client = c
|
||||
return c
|
||||
}
|
||||
|
||||
// Do serves the file according to the request URL.
|
||||
func (c *MockClient) Do(req *http.Request) (*http.Response, error) {
|
||||
filename := filepath.Join(c.dir, path.Base(req.URL.Path))
|
||||
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
resp := http.Response{
|
||||
Status: http.StatusText(http.StatusNotFound),
|
||||
StatusCode: http.StatusNotFound,
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
cType, ok := contentTypes[path.Ext(filename)]
|
||||
if !ok {
|
||||
cType = "application/octet-stream"
|
||||
}
|
||||
rdr := respond(cType, data)
|
||||
resp, err := http.ReadResponse(bufio.NewReader(rdr), req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func respond(cType string, data []byte) io.Reader {
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString("HTTP/1.1 200 OK\n")
|
||||
buf.WriteString(fmt.Sprintf("Content-Type: %s\n\n", cType))
|
||||
_, err := buf.Write(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &buf
|
||||
}
|
||||
|
||||
// MockServer creates a mock HTTP server and installs its client
|
||||
// instead of the default one. Serves responses from the file system
|
||||
// instead of remote calls. Should be used for testing purposes only.
|
||||
func MockServer() *httptest.Server {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
filename := filepath.Join("testdata", path.Base(r.URL.Path))
|
||||
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
cType, ok := contentTypes[path.Ext(filename)]
|
||||
if !ok {
|
||||
cType = "application/octet-stream"
|
||||
}
|
||||
|
||||
w.Header().Set("content-type", cType)
|
||||
_, err = w.Write(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}))
|
||||
client = srv.Client()
|
||||
return srv
|
||||
}
|
||||
34
internal/httpx/mock_test.go
Normal file
34
internal/httpx/mock_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMockClient(t *testing.T) {
|
||||
Mock()
|
||||
|
||||
const url = "https://codapi.org/example.txt"
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
|
||||
resp, err := Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("Do: unexpected error %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Do: expected status code %d, got %d", http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("io.ReadAll: unexpected error %v", err)
|
||||
}
|
||||
|
||||
want := "hello"
|
||||
if string(body) != want {
|
||||
t.Errorf("Do: expected %v, got %v", want, string(body))
|
||||
}
|
||||
}
|
||||
1
internal/httpx/testdata/example.json
vendored
Normal file
1
internal/httpx/testdata/example.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "alice" }
|
||||
1
internal/httpx/testdata/example.txt
vendored
Normal file
1
internal/httpx/testdata/example.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello
|
||||
Reference in New Issue
Block a user