From 64a20b1dfeae6eaf58ca7e49fc386deb38c5acaf Mon Sep 17 00:00:00 2001 From: Carlana Johnson Date: Mon, 23 Dec 2024 09:23:26 -0500 Subject: [PATCH 1/4] Add reqtest.Recorder --- builder_example_test.go | 3 ++- go.mod | 2 +- reqtest/recorder.go | 42 ++++++++++++++++++++++++++++++++ reqtest/recorder_example_test.go | 15 +++++++++--- reqtest/recorder_test.go | 8 +++--- reqtest/recordermode_string.go | 25 +++++++++++++++++++ reqtest/transport.go | 8 ++++++ reqxml/to_example_test.go | 3 +-- 8 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 reqtest/recorder.go create mode 100644 reqtest/recordermode_string.go diff --git a/builder_example_test.go b/builder_example_test.go index 7c4ed85..5a38e90 100644 --- a/builder_example_test.go +++ b/builder_example_test.go @@ -17,10 +17,11 @@ import ( "strings" "github.com/carlmjohnson/requests" + "github.com/carlmjohnson/requests/reqtest" ) func init() { - http.DefaultClient.Transport = requests.Replay("testdata") + http.DefaultClient.Transport = reqtest.Recorder(reqtest.ModeReplay, nil, "testdata") } func Example() { diff --git a/go.mod b/go.mod index 87f435e..7dbf66b 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/carlmjohnson/requests -go 1.22 +go 1.23 require golang.org/x/net v0.33.0 diff --git a/reqtest/recorder.go b/reqtest/recorder.go new file mode 100644 index 0000000..f89aa34 --- /dev/null +++ b/reqtest/recorder.go @@ -0,0 +1,42 @@ +package reqtest + +import ( + "net/http" + + "github.com/carlmjohnson/requests" +) + +// RecorderMode is an argument type controlling [Recorder]. +type RecorderMode int8 + +//go:generate stringer -type=RecorderMode + +// Enum values for type RecorderMode +const ( + // Record HTTP requests and responses to text files. + ModeRecord RecorderMode = iota + // Replay responses from pre-recorded text files. + ModeReplay + // Replay responses from pre-recorded files if present, + // otherwise record a new request response pair. + ModeCache +) + +// Recorder returns an HTTP transport that operates in the specified mode. +// Requests and responses are read from or written to +// text files in basepath according to a hash of their contents. +// File names may optionally be prefixed with comments for better human organization. +// The http.RoundTripper is only used in ModeRecord and ModeCache +// and if nil defaults to http.DefaultTransport. +func Recorder(mode RecorderMode, rt http.RoundTripper, basepath string) requests.Transport { + switch mode { + case ModeReplay: + return Replay(basepath) + case ModeRecord: + return Record(rt, basepath) + case ModeCache: + return Caching(rt, basepath) + default: + panic("invalid reqtest.RecorderMode") + } +} diff --git a/reqtest/recorder_example_test.go b/reqtest/recorder_example_test.go index eb7ec25..83f23b3 100644 --- a/reqtest/recorder_example_test.go +++ b/reqtest/recorder_example_test.go @@ -3,6 +3,7 @@ package reqtest_test import ( "context" "fmt" + "os" "testing/fstest" "github.com/carlmjohnson/requests" @@ -28,8 +29,12 @@ An example response.` // true } -func ExampleReplayFS() { - fsys := fstest.MapFS{ +func ExampleRecorder() { + dir, err := os.MkdirTemp("", "") + if err != nil { + panic(err) + } + err = os.CopyFS(dir, fstest.MapFS{ "fsys.example - MKIYDwjs.res.txt": &fstest.MapFile{ Data: []byte(`HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 @@ -37,12 +42,16 @@ Date: Mon, 24 May 2021 18:48:50 GMT An example response.`), }, + }) + if err != nil { + panic(err) } + var s string const expected = `An example response.` if err := requests. URL("http://fsys.example"). - Transport(reqtest.ReplayFS(fsys)). + Transport(reqtest.Recorder(reqtest.ModeReplay, nil, dir)). ToString(&s). Fetch(context.Background()); err != nil { panic(err) diff --git a/reqtest/recorder_test.go b/reqtest/recorder_test.go index 7d1453b..f69fd70 100644 --- a/reqtest/recorder_test.go +++ b/reqtest/recorder_test.go @@ -13,7 +13,7 @@ import ( "github.com/carlmjohnson/requests/reqtest" ) -func TestRecordReplay(t *testing.T) { +func TestRecorder(t *testing.T) { baseTrans := requests.ReplayString(`HTTP/1.1 200 OK Test Document 1`) @@ -21,13 +21,13 @@ Test Document 1`) var s1, s2 string err := requests.URL("http://example.com"). - Transport(reqtest.Record(baseTrans, dir)). + Transport(reqtest.Recorder(reqtest.ModeRecord, baseTrans, dir)). ToString(&s1). Fetch(context.Background()) be.NilErr(t, err) err = requests.URL("http://example.com"). - Transport(reqtest.Replay(dir)). + Transport(reqtest.Recorder(reqtest.ModeReplay, nil, dir)). ToString(&s2). Fetch(context.Background()) be.NilErr(t, err) @@ -48,7 +48,7 @@ func TestCaching(t *testing.T) { } return } - trans := reqtest.Caching(onceTrans, dir) + trans := reqtest.Recorder(reqtest.ModeCache, onceTrans, dir) var s1, s2 string err := requests.URL("http://example.com"). Transport(trans). diff --git a/reqtest/recordermode_string.go b/reqtest/recordermode_string.go new file mode 100644 index 0000000..8f690e2 --- /dev/null +++ b/reqtest/recordermode_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=RecorderMode"; DO NOT EDIT. + +package reqtest + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ModeRecord-0] + _ = x[ModeReplay-1] + _ = x[ModeCache-2] +} + +const _RecorderMode_name = "ModeRecordModeReplayModeCache" + +var _RecorderMode_index = [...]uint8{0, 10, 20, 29} + +func (i RecorderMode) String() string { + if i < 0 || i >= RecorderMode(len(_RecorderMode_index)-1) { + return "RecorderMode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _RecorderMode_name[_RecorderMode_index[i]:_RecorderMode_index[i+1]] +} diff --git a/reqtest/transport.go b/reqtest/transport.go index cfdefea..fad49ce 100644 --- a/reqtest/transport.go +++ b/reqtest/transport.go @@ -17,6 +17,8 @@ func ReplayString(rawResponse string) requests.Transport { // requests and their responses to text files in basepath. // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. +// +// Deprecated: Use [Recorder]. func Record(rt http.RoundTripper, basepath string) requests.Transport { return requests.Record(rt, basepath) } @@ -25,6 +27,8 @@ func Record(rt http.RoundTripper, basepath string) requests.Transport { // responses from text files in basepath. // Responses are looked up according to a hash of the request. // Response file names may optionally be prefixed with comments for better human organization. +// +// Deprecated: Use [Recorder]. func Replay(basepath string) requests.Transport { return requests.Replay(basepath) } @@ -33,6 +37,8 @@ func Replay(basepath string) requests.Transport { // responses from text files in the fs.FS. // Responses are looked up according to a hash of the request. // Response file names may optionally be prefixed with comments for better human organization. +// +// Deprecated: Use os.CopyFS and [Recorder]. func ReplayFS(fsys fs.FS) requests.Transport { return requests.ReplayFS(fsys) } @@ -42,6 +48,8 @@ func ReplayFS(fsys fs.FS) requests.Transport { // it caches the result of issuing the request with rt in basepath. // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. +// +// Deprecated: Use [Recorder]. func Caching(rt http.RoundTripper, basepath string) requests.Transport { return requests.Caching(rt, basepath) } diff --git a/reqxml/to_example_test.go b/reqxml/to_example_test.go index 913fe4b..5999a97 100644 --- a/reqxml/to_example_test.go +++ b/reqxml/to_example_test.go @@ -11,8 +11,7 @@ import ( ) func init() { - http.DefaultClient.Transport = reqtest.Replay("testdata") - // http.DefaultClient.Transport = reqtest.Caching(nil, "testdata") + http.DefaultClient.Transport = reqtest.Recorder(reqtest.ModeReplay, nil, "testdata") } func ExampleTo() { From 1f213253d33fca9aabb9a47674b40d995e9caee8 Mon Sep 17 00:00:00 2001 From: Carlana Johnson Date: Mon, 23 Dec 2024 09:48:29 -0500 Subject: [PATCH 2/4] Docs: Clean up reqtest.Recorder docs a little --- reqtest/recorder.go | 10 ++++----- reqtest/recorder_example_test.go | 35 +++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/reqtest/recorder.go b/reqtest/recorder.go index f89aa34..40b6512 100644 --- a/reqtest/recorder.go +++ b/reqtest/recorder.go @@ -2,8 +2,6 @@ package reqtest import ( "net/http" - - "github.com/carlmjohnson/requests" ) // RecorderMode is an argument type controlling [Recorder]. @@ -18,7 +16,7 @@ const ( // Replay responses from pre-recorded text files. ModeReplay // Replay responses from pre-recorded files if present, - // otherwise record a new request response pair. + // otherwise record a new request/response pair. ModeCache ) @@ -26,9 +24,9 @@ const ( // Requests and responses are read from or written to // text files in basepath according to a hash of their contents. // File names may optionally be prefixed with comments for better human organization. -// The http.RoundTripper is only used in ModeRecord and ModeCache -// and if nil defaults to http.DefaultTransport. -func Recorder(mode RecorderMode, rt http.RoundTripper, basepath string) requests.Transport { +// The http.RoundTripper is only used in [ModeRecord] and [ModeCache] +// and if nil defaults to [http.DefaultTransport]. +func Recorder(mode RecorderMode, rt http.RoundTripper, basepath string) http.RoundTripper { switch mode { case ModeReplay: return Replay(basepath) diff --git a/reqtest/recorder_example_test.go b/reqtest/recorder_example_test.go index 83f23b3..4d0b230 100644 --- a/reqtest/recorder_example_test.go +++ b/reqtest/recorder_example_test.go @@ -29,34 +29,49 @@ An example response.` // true } -func ExampleRecorder() { +func copyToTempDir(m map[string]string) string { dir, err := os.MkdirTemp("", "") if err != nil { panic(err) } - err = os.CopyFS(dir, fstest.MapFS{ - "fsys.example - MKIYDwjs.res.txt": &fstest.MapFile{ - Data: []byte(`HTTP/1.1 200 OK + fsys := make(fstest.MapFS, len(m)) + for path, content := range m { + fsys[path] = &fstest.MapFile{ + Data: []byte(content), + } + } + if err = os.CopyFS(dir, fsys); err != nil { + panic(err) + } + return dir +} + +func ExampleRecorder() { + // Given a directory with the following file + dir := copyToTempDir(map[string]string{ + "fsys.example - MKIYDwjs.res.txt": `HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Date: Mon, 24 May 2021 18:48:50 GMT -An example response.`), - }, +An example response.`, }) - if err != nil { - panic(err) - } + defer os.RemoveAll(dir) + + // Make a test transport that reads the directory + tr := reqtest.Recorder(reqtest.ModeReplay, nil, dir) + // And test that it produces the correct response var s string const expected = `An example response.` if err := requests. URL("http://fsys.example"). - Transport(reqtest.Recorder(reqtest.ModeReplay, nil, dir)). + Transport(tr). ToString(&s). Fetch(context.Background()); err != nil { panic(err) } fmt.Println(s == expected) + // Output: // true } From eb9ee6f5401eb74fa29fe2d6c2821ae3d0eb588e Mon Sep 17 00:00:00 2001 From: Carlana Date: Thu, 26 Dec 2024 12:22:48 -0500 Subject: [PATCH 3/4] Docs: Update deprecation warnings --- recorder.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/recorder.go b/recorder.go index a493ead..b3af2e1 100644 --- a/recorder.go +++ b/recorder.go @@ -19,7 +19,7 @@ import ( // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. // -// Deprecated: Use reqtest.Record. +// Deprecated: Use reqtest.Recorder. func Record(rt http.RoundTripper, basepath string) Transport { if rt == nil { rt = http.DefaultTransport @@ -59,7 +59,7 @@ func Record(rt http.RoundTripper, basepath string) Transport { // responses from text files in basepath. // Responses are looked up according to a hash of the request. // -// Deprecated: Use reqtest.Replay. +// Deprecated: Use reqtest.Recorder. func Replay(basepath string) Transport { return ReplayFS(os.DirFS(basepath)) } @@ -71,7 +71,7 @@ var errNotFound = errors.New("response not found") // Responses are looked up according to a hash of the request. // Response file names may optionally be prefixed with comments for better human organization. // -// Deprecated: Use reqtest.ReplayFS. +// Deprecated: Use reqtest.Recorder and os.CopyFS. func ReplayFS(fsys fs.FS) Transport { return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { defer func() { @@ -117,7 +117,7 @@ func buildName(b []byte) (reqname, resname string) { // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. // -// Deprecated: Use reqtest.Caching. +// Deprecated: Use reqtest.Recorder. func Caching(rt http.RoundTripper, basepath string) Transport { replay := Replay(basepath).RoundTrip record := Record(rt, basepath).RoundTrip From 5e131b0e9890c576874eafc97b3aee3098558ab1 Mon Sep 17 00:00:00 2001 From: Carlana Date: Thu, 26 Dec 2024 12:26:37 -0500 Subject: [PATCH 4/4] Docs: Links --- config.go | 2 +- recorder.go | 8 ++++---- transport.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index ba4ef61..758e04c 100644 --- a/config.go +++ b/config.go @@ -37,7 +37,7 @@ func GzipConfig(level int, h func(gw *gzip.Writer) error) Config { // which sets the Builder's BaseURL to s.URL // and the Builder's Client to s.Client(). // -// Deprecated: Use reqtest.Server. +// Deprecated: Use [reqtest.Server]. func TestServerConfig(s *httptest.Server) Config { return func(rb *Builder) { rb. diff --git a/recorder.go b/recorder.go index b3af2e1..da2f6e9 100644 --- a/recorder.go +++ b/recorder.go @@ -19,7 +19,7 @@ import ( // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. // -// Deprecated: Use reqtest.Recorder. +// Deprecated: Use [reqtest.Recorder]. func Record(rt http.RoundTripper, basepath string) Transport { if rt == nil { rt = http.DefaultTransport @@ -59,7 +59,7 @@ func Record(rt http.RoundTripper, basepath string) Transport { // responses from text files in basepath. // Responses are looked up according to a hash of the request. // -// Deprecated: Use reqtest.Recorder. +// Deprecated: Use [reqtest.Recorder]. func Replay(basepath string) Transport { return ReplayFS(os.DirFS(basepath)) } @@ -71,7 +71,7 @@ var errNotFound = errors.New("response not found") // Responses are looked up according to a hash of the request. // Response file names may optionally be prefixed with comments for better human organization. // -// Deprecated: Use reqtest.Recorder and os.CopyFS. +// Deprecated: Use [reqtest.Recorder] and [os.CopyFS]. func ReplayFS(fsys fs.FS) Transport { return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { defer func() { @@ -117,7 +117,7 @@ func buildName(b []byte) (reqname, resname string) { // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. // -// Deprecated: Use reqtest.Recorder. +// Deprecated: Use [reqtest.Recorder]. func Caching(rt http.RoundTripper, basepath string) Transport { replay := Replay(basepath).RoundTrip record := Record(rt, basepath).RoundTrip diff --git a/transport.go b/transport.go index 48bd317..2f552d6 100644 --- a/transport.go +++ b/transport.go @@ -26,7 +26,7 @@ var _ Transport = RoundTripFunc(nil) // ReplayString returns an http.RoundTripper that always responds with a // request built from rawResponse. It is intended for use in one-off tests. // -// Deprecated: Use reqtest.ReplayString. +// Deprecated: Use [reqtest.ReplayString]. func ReplayString(rawResponse string) Transport { return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { r := bufio.NewReader(strings.NewReader(rawResponse))