blob: 87d74cc7afba07f62ec408d754623dc804f42199 [file] [log] [blame]
package mockhttpclient
import (
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"sync"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/util"
)
func TestBasic(t *testing.T) {
// This is the example in the documentation.
r := chi.NewRouter()
r.With(SchemeMatcher("https"), HostMatcher("www.google.com")).
Get("/", MockGetDialogue([]byte("Here's a response.")).ServeHTTP)
client := NewMuxClient(r)
res, err := client.Get("https://www.google.com")
require.NoError(t, err)
respBody, err := io.ReadAll(res.Body)
require.NoError(t, err)
require.Equal(t, []byte("Here's a response."), respBody)
}
func TestVars(t *testing.T) {
// This is the example in the documentation.
r := chi.NewRouter()
expectedResponse := "Success."
r.With(HostMatcher("example.com"), QueryMatcher("name", "foo", "size", "42")).
Post("/add/{id:[a-zA-Z0-9]+}", func(w http.ResponseWriter, r *http.Request) {
t := MuxSafeT(t)
values, err := url.ParseQuery(r.URL.RawQuery)
require.NoError(t, err)
require.Equal(t, chi.URLParam(r, "id"), "foo")
require.Equal(t, values.Get("name"), "foo")
_, err = w.Write([]byte(expectedResponse))
require.NoError(t, err)
})
client := NewMuxClient(r)
resp, err := client.Post("http://example.com/add/foo?name=foo&size=42", "", nil)
require.NoError(t, err)
actualResponse, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, expectedResponse, string(actualResponse))
}
// mockTestingT mocks expect.TestingT.
type mockTestingT struct {
errors []string
}
func (t *mockTestingT) Errorf(format string, args ...interface{}) {
t.errors = append(t.errors, fmt.Sprintf(format, args...))
}
func TestAssertionFailure(t *testing.T) {
mockT := &mockTestingT{}
r := chi.NewRouter()
r.With(HostMatcher("example.com"), QueryMatcher("name", "bar", "size", "42")).
Post("/add/{id:[a-zA-Z0-9]+}", func(w http.ResponseWriter, r *http.Request) {
t := MuxSafeT(mockT)
values, err := url.ParseQuery(r.URL.RawQuery)
require.NoError(t, err)
require.Equal(t, chi.URLParam(r, "id"), values.Get("name"))
})
client := NewMuxClient(r)
_, err := client.Post("http://example.com/add/foo?name=bar&size=42", "", nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Test failed")
require.Contains(t, err.Error(), "while handling HTTP request for http://example.com/add/foo?name=bar&size=42")
require.Equal(t, 1, len(mockT.errors))
re := regexp.MustCompile(`Not equal:\s+expected:\s+"foo"\s+actual\s+:\s+"bar"\s*`)
require.True(t, re.MatchString(mockT.errors[0]), "Expected test failure message to match regexp %q, but got %q", re, mockT.errors[0])
}
func TestMissingHandler(t *testing.T) {
r := chi.NewRouter()
handlerCalled := false
r.With(HostMatcher("example.com")).
Post("/remove/{id:[a-zA-Z0-9]+}", func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
})
client := NewMuxClient(r)
_, err := client.Post("http://example.com/add/foo?name=foo&size=42", "", nil)
require.Error(t, err)
require.Contains(t, err.Error(), "No matching handler for http://example.com/add/foo?name=foo&size=42")
require.False(t, handlerCalled)
}
func TestErrorResponse(t *testing.T) {
r := chi.NewRouter()
r.With(SchemeMatcher("https"), HostMatcher("www.google.com")).
Get("/", MockGetError("TODO(benjaminwagner)", http.StatusTeapot).ServeHTTP)
client := NewMuxClient(r)
res, err := client.Get("https://www.google.com")
require.NoError(t, err)
require.Equal(t, http.StatusTeapot, res.StatusCode)
}
// doStreamingRequestAndCheckBodyClosed performs a POST request to url using client with a streaming
// body. Asserts that the body is closed by client within 10 seconds. Returns the result of
// client.Post.
func doStreamingRequestAndAssertBodyClosed(t *testing.T, client *http.Client, url string) (*http.Response, error) {
reader, writer := io.Pipe()
abort := time.After(10 * time.Second)
aborted := false
var writeErr error
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer util.Close(writer)
defer wg.Done()
for {
select {
case <-abort:
aborted = true
return
default:
}
_, writeErr = writer.Write([]byte("aaaaaaaaaaaaaaaa"))
if writeErr != nil {
return
}
}
}()
resp, err := client.Post(url, "text/plain", reader)
wg.Wait()
require.False(t, aborted)
require.Equal(t, io.ErrClosedPipe, writeErr)
return resp, err
}
func TestStreamingBodyClosedForEmptyHandler(t *testing.T) {
r := chi.NewRouter()
r.With(HostMatcher("example.com")).
Post("/add/{id:[a-zA-Z0-9]+}", func(w http.ResponseWriter, r *http.Request) {})
client := NewMuxClient(r)
_, err := doStreamingRequestAndAssertBodyClosed(t, client, "http://example.com/add/foo")
require.NoError(t, err)
}
func TestStreamingBodyClosedForMissingHandler(t *testing.T) {
r := chi.NewRouter()
handlerCalled := false
r.With(HostMatcher("example.com")).
Post("/add/{id:[a-zA-Z0-9]+}", func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
})
client := NewMuxClient(r)
_, err := doStreamingRequestAndAssertBodyClosed(t, client, "http://example.com/remove/foo")
require.Error(t, err)
require.Contains(t, err.Error(), "No matching handler for http://example.com/remove/foo")
require.False(t, handlerCalled)
}
func TestStreamingBodyClosedForInvalidURL(t *testing.T) {
r := chi.NewRouter()
handlerCalled := false
r.With(HostMatcher("example.com")).
Post("/add/{id:[a-zA-Z0-9]+}", func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
})
client := NewMuxClient(r)
_, err := doStreamingRequestAndAssertBodyClosed(t, client, "http:///remove/foo")
require.Error(t, err)
require.Contains(t, err.Error(), "invalid request")
require.False(t, handlerCalled)
}
func TestMockDialogueFailureInMuxClient(t *testing.T) {
r := chi.NewRouter()
r.With(SchemeMatcher("https"), HostMatcher("www.google.com")).
Post("/", MockGetDialogue([]byte("Here's a response.")).ServeHTTP)
client := NewMuxClient(r)
_, err := client.Post("https://www.google.com", "", nil)
require.Error(t, err)
require.Contains(t, err.Error(), `Wrong Method, expected "GET", but was "POST"`)
}