From 86848d2d51dcdbb0c7c087fabf7f3c16954bfbd0 Mon Sep 17 00:00:00 2001 From: Livan Du Date: Fri, 21 Jun 2024 15:58:12 -0400 Subject: [PATCH 1/4] revised ittest package to better support tests that doesn't use apptest.Bootstrap() --- test/ittest/httpvcr.go | 198 +++++++++++------ test/ittest/httpvcr_hooks.go | 6 +- test/ittest/httpvcr_test.go | 200 +++++++++++------- .../HttpVCRTestMoreRecords.httpvcr.yaml | 137 ++++++++++++ .../testdata/HttpVCRTestRecords.httpvcr.yaml | 6 +- test/opensearchtest/recorder.go | 2 +- 6 files changed, 407 insertions(+), 142 deletions(-) create mode 100644 test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml diff --git a/test/ittest/httpvcr.go b/test/ittest/httpvcr.go index b9b1d579..7b4fc84c 100644 --- a/test/ittest/httpvcr.go +++ b/test/ittest/httpvcr.go @@ -32,6 +32,7 @@ import ( "net/http" "strconv" "testing" + "time" ) func init() { @@ -49,36 +50,34 @@ type RecorderDI struct { HTTPVCROption *HTTPVCROption } +type recorderDI struct { + fx.In + RecorderDI + HTTPRecorder *HttpRecorder +} + // WithHttpPlayback enables remote HTTP server playback capabilities supported by `httpvcr` // This mode requires apptest.Bootstrap to work // Each top-level test should have corresponding recorded HTTP responses in `testdata` folder, or the test will fail. // To enable record mode, use `go test ... --record-http` at CLI, or do it programmatically with HttpRecordingMode // See https://github.com/cockroachdb/copyist for more details func WithHttpPlayback(t *testing.T, opts ...HTTPVCROptions) test.Options { - initial := HTTPVCROption{ - Name: t.Name(), - SavePath: "testdata", - RecordMatching: nil, - Hooks: []RecorderHook{ - NewRecorderHook(FixedDurationHook(DefaultHTTPDuration), recorder.BeforeSaveHook), - NewRecorderHook(InteractionIndexAwareHook(), recorder.BeforeSaveHook), - NewRecorderHook(SanitizingHook(), recorder.BeforeSaveHook), - }, - SkipRequestLatency: true, - indexAwareWrapper: newIndexAwareMatcherWrapper(), // enforce order - } + opts = append([]HTTPVCROptions{ + HttpRecordName(t.Name()), + SanitizeHttpRecord(), + }, opts...) - var di RecorderDI + var di recorderDI testOpts := []test.Options{ apptest.WithDI(&di), apptest.WithFxOptions( fx.Provide( - httpRecorderProvider(initial, opts), + httpRecorderProvider(opts), ), fx.Invoke(httpRecorderCleanup), ), test.SubTestSetup(recorderDISetup(&di)), - test.SubTestTeardown(recorderReset(&di)), + test.SubTestTeardown(recorderReset()), } return test.WithOptions(testOpts...) } @@ -87,9 +86,17 @@ func WithHttpPlayback(t *testing.T, opts ...HTTPVCROptions) test.Options { Functions ****************************/ +// Recorder extract HttpRecorder from given context. If HttpRecorder is not available, it returns nil +func Recorder(ctx context.Context) *HttpRecorder { + if rec, ok := ctx.Value(ckRecorder).(*HttpRecorder); ok && rec.Recorder != nil { + return rec + } + return nil +} + // Client extract http.Client that provided by Recorder. If Recorder is not available, it returns nil func Client(ctx context.Context) *http.Client { - if rec, ok := ctx.Value(ckRecorder).(*recorder.Recorder); ok && rec != nil { + if rec, ok := ctx.Value(ckRecorder).(*HttpRecorder); ok && rec.Recorder != nil { return rec.GetDefaultClient() } return nil @@ -97,31 +104,48 @@ func Client(ctx context.Context) *http.Client { // IsRecording returns true if HTTP VCR is in recording mode func IsRecording(ctx context.Context) bool { - if rec, ok := ctx.Value(ckRecorder).(*recorder.Recorder); ok && rec != nil { + if rec, ok := ctx.Value(ckRecorder).(*HttpRecorder); ok && rec.Recorder != nil { return rec.IsRecording() } return false } -// AdditionalMatcherOptions temporarily add additional RecordMatcherOptions to the current test context -// on top of test's HTTPVCROptions. -// Note: The additional options take effect within the scope of sub-test. For test level options, use HttpRecordMatching +// AdditionalMatcherOptions temporarily add additional RecordMatcherOptions to the current test context on top of test's HTTPVCROptions. +// Any changes made with this method can be reset via ResetRecorder. When using with WithHttpPlayback(), the reset is automatic per sub-test +// Note: The additional options take effect within the scope of sub-test. For test level options, use HttpRecordMatching. func AdditionalMatcherOptions(ctx context.Context, opts ...RecordMatcherOptions) { - rec, ok := ctx.Value(ckRecorder).(*recorder.Recorder) - if !ok || rec == nil { + rec, ok := ctx.Value(ckRecorder).(*HttpRecorder) + if !ok || rec.Recorder == nil { return } // merge matching options - opt := ctx.Value(ckRecorderOption).(*HTTPVCROption) - newOpts := make([]RecordMatcherOptions, len(opt.RecordMatching), len(opt.RecordMatching)+len(opts)) - copy(newOpts, opt.RecordMatching) + newOpts := make([]RecordMatcherOptions, len(rec.Options.RecordMatching), len(rec.Options.RecordMatching)+len(opts)) + copy(newOpts, rec.Options.RecordMatching) newOpts = append(newOpts, opts...) // construct and set new matcher - newMatcher := newCassetteMatcherFunc(newOpts, opt.indexAwareWrapper) + newMatcher := newCassetteMatcherFunc(newOpts, rec.Options.indexAwareWrapper) rec.SetMatcher(newMatcher) } +// ResetRecorder revert the change made by AdditionalMatcherOptions. +func ResetRecorder(ctx context.Context) { + rec, ok := ctx.Value(ckRecorder).(*HttpRecorder) + if !ok || rec.Recorder == nil { + return + } + rec.SetMatcher(rec.Matcher) +} + +// StopRecorder stops the recorder extracted from the given context. +func StopRecorder(ctx context.Context) error { + rec, ok := ctx.Value(ckRecorder).(*HttpRecorder) + if !ok || rec.Recorder == nil { + return fmt.Errorf("failed to stop recorder: no recorder found in context") + } + return rec.Stop() +} + /************************* Options *************************/ @@ -192,17 +216,29 @@ func HttpRecordIgnoreHost() HTTPVCROptions { return HttpRecordMatching(IgnoreHost()) } +// HttpRecordOrdering toggles HTTP interactions order matching. +// When enforced, HTTP interactions have to happen in the recorded order. +// Otherwise, HTTP interactions can happen in any order, but each matched record can only replay once +// By default, record ordering is enabled +func HttpRecordOrdering(enforced bool) HTTPVCROptions { + return func(opt *HTTPVCROption) { + if enforced && opt.indexAwareWrapper == nil { + opt.indexAwareWrapper = newIndexAwareMatcherWrapper() + } else if !enforced { + opt.indexAwareWrapper = nil + } + } +} + // DisableHttpRecordOrdering disable HTTP interactions order matching. // By default, HTTP interactions have to happen in the recorded order. // When this option is used, HTTP interactions can happen in any order. However, each matched record can only replay once func DisableHttpRecordOrdering() HTTPVCROptions { - return func(opt *HTTPVCROption) { - opt.indexAwareWrapper = nil - } + return HttpRecordOrdering(false) } // HttpTransport override the RealTransport during recording mode. This option has no effect in playback mode -func HttpTransport(transport *http.Transport) HTTPVCROptions { +func HttpTransport(transport http.RoundTripper) HTTPVCROptions { return func(opt *HTTPVCROption) { opt.RealTransport = transport } @@ -215,27 +251,44 @@ func ApplyHttpLatency() HTTPVCROptions { } } +// SanitizeHttpRecord install a hook to sanitize request and response before they are saved in file. +// See SanitizingHook for details. +func SanitizeHttpRecord() HTTPVCROptions { + return func(opt *HTTPVCROption) { + opt.Hooks = append(opt.Hooks, NewRecorderHook(SanitizingHook(), recorder.BeforeSaveHook)) + } +} + +// FixedHttpRecordDuration install a hook to set a fixed duration on interactions before they are saved. +// Otherwise, the actual latency will be recorded. +// When HTTPVCROption.SkipRequestLatency is set to false, the recorded duration will be applied during playback +// See FixedDurationHook for details. +func FixedHttpRecordDuration(duration time.Duration) HTTPVCROptions { + return func(opt *HTTPVCROption) { + opt.Hooks = append(opt.Hooks, NewRecorderHook(FixedDurationHook(duration), recorder.BeforeSaveHook)) + } +} + /**************************** - Recorder Aware Context + RawRecorder Aware Context ****************************/ type recorderCtxKey struct{} -type optionCtxKey struct{} var ckRecorder = recorderCtxKey{} -var ckRecorderOption = optionCtxKey{} type recorderAwareContext struct { context.Context - recorder *recorder.Recorder - origOption *HTTPVCROption + recorder *HttpRecorder } -func contextWithRecorder(parent context.Context, rec *recorder.Recorder, opt *HTTPVCROption) *recorderAwareContext { +func contextWithRecorder(parent context.Context, rec *HttpRecorder) context.Context { + if rec == nil { + return parent + } return &recorderAwareContext{ - Context: parent, - recorder: rec, - origOption: opt, + Context: parent, + recorder: rec, } } @@ -243,37 +296,31 @@ func (c *recorderAwareContext) Value(k interface{}) interface{} { switch k { case ckRecorder: return c.recorder - case ckRecorderOption: - return c.origOption default: return c.Context.Value(k) } } -func recorderDISetup(di *RecorderDI) test.SetupFunc { +func recorderDISetup(di *recorderDI) test.SetupFunc { return func(ctx context.Context, t *testing.T) (context.Context, error) { - return contextWithRecorder(ctx, di.Recorder, di.HTTPVCROption), nil + return contextWithRecorder(ctx, di.HTTPRecorder), nil } } -// recorderReset reset recorder to original state in case it changed -func recorderReset(di *RecorderDI) test.TeardownFunc { +// recorderReset automatically reset recorder to original state in case it changed +func recorderReset() test.TeardownFunc { return func(ctx context.Context, t *testing.T) error { - rec, ok := ctx.Value(ckRecorder).(*recorder.Recorder) - if !ok { - return nil - } - rec.SetMatcher(di.RecorderMatcher) + ResetRecorder(ctx) return nil } } /************************* - Internals + HttpRecorder *************************/ -// HttpRecorder wrapper of recorder.Recorder, used to hold some value that normally inaccessible via wrapped recorder.Recorder. -// Note: Internal Use Only! this type is for other test utilities to re-configure recorder.Recorder +// HttpRecorder wrapper of recorder.RawRecorder, used to hold some value that normally inaccessible via wrapped recorder.RawRecorder. +// Note: This type is for other test utilities to re-configure recorder.RawRecorder type HttpRecorder struct { *recorder.Recorder RawOptions *recorder.Options @@ -281,10 +328,29 @@ type HttpRecorder struct { Options *HTTPVCROption } -// NewHttpRecorder Internal Use Only! Create a new HttpRecorder, commonly used by other test utilities that relies on -// http recording. (e.g. opensearchtest, consultest, etc.) +// ContextWithNewHttpRecorder is a convenient function that create a new HTTP recorder and store it in context. +// The returned context can be used with context value accessor such as Client(ctx), IsRecording(ctx), AdditionalMatcherOptions(ctx), etc. +// See NewHttpRecorder +func ContextWithNewHttpRecorder(ctx context.Context, opts ...HTTPVCROptions) (context.Context, error) { + rec, e := NewHttpRecorder(opts...) + if e != nil { + return nil, e + } + return contextWithRecorder(ctx, rec), nil +} + +// NewHttpRecorder create a new HttpRecorder. Commonly used by: +// - other test utilities that relies on http recording. (e.g. opensearchtest, consultest, etc.) +// - unit tests that doesn't bootstrap dependency injection func NewHttpRecorder(opts ...HTTPVCROptions) (*HttpRecorder, error) { - var opt HTTPVCROption + opt := HTTPVCROption{ + SavePath: "testdata", + Hooks: []RecorderHook{ + NewRecorderHook(InteractionIndexAwareHook(), recorder.BeforeSaveHook), + }, + SkipRequestLatency: true, + indexAwareWrapper: newIndexAwareMatcherWrapper(), // enforce order + } for _, fn := range opts { fn(&opt) } @@ -311,6 +377,10 @@ func NewHttpRecorder(opts ...HTTPVCROptions) (*HttpRecorder, error) { }, nil } +/************************* + Internals + *************************/ + type vcrDI struct { fx.In VCROptions []HTTPVCROptions `group:"http-vcr"` @@ -318,26 +388,24 @@ type vcrDI struct { type vcrOut struct { fx.Out - Recorder *recorder.Recorder + HTTPRecorder *HttpRecorder + RawRecorder *recorder.Recorder CassetteMatcher cassette.MatcherFunc HttpVCROption *HTTPVCROption RawRecorderOption *recorder.Options HttpClientCustomizer httpclient.ClientCustomizer `group:"http-client"` } -func httpRecorderProvider(initial HTTPVCROption, opts []HTTPVCROptions) func(di vcrDI) (vcrOut, error) { +func httpRecorderProvider(opts []HTTPVCROptions) func(di vcrDI) (vcrOut, error) { return func(di vcrDI) (vcrOut, error) { - initialOpt := func(opt *HTTPVCROption) { - *opt = initial - } - finalOpts := append([]HTTPVCROptions{initialOpt}, opts...) - finalOpts = append(finalOpts, di.VCROptions...) + finalOpts := append(opts, di.VCROptions...) rec, e := NewHttpRecorder(finalOpts...) if e != nil { return vcrOut{}, e } return vcrOut{ - Recorder: rec.Recorder, + HTTPRecorder: rec, + RawRecorder: rec.Recorder, CassetteMatcher: rec.Matcher, HttpVCROption: rec.Options, RawRecorderOption: rec.RawOptions, @@ -378,7 +446,7 @@ func toRecorderOptions(opt HTTPVCROption) *recorder.Options { default: } - name := opt.Name + name := opt.Name + ".httpvcr" if len(opt.SavePath) != 0 { name = opt.SavePath + "/" + opt.Name + ".httpvcr" } diff --git a/test/ittest/httpvcr_hooks.go b/test/ittest/httpvcr_hooks.go index ddb3f8d7..d3419212 100644 --- a/test/ittest/httpvcr_hooks.go +++ b/test/ittest/httpvcr_hooks.go @@ -139,7 +139,11 @@ func InteractionIndexAwareHook() func(i *cassette.Interaction) error { } } -// SanitizingHook is a httpvcr hook that sanitize values in header, query, body (x-form-urlencoded/json) +// SanitizingHook is an HTTP VCR hook that sanitize values in header, query, body (x-form-urlencoded/json). +// Values to sanitize are globally configured via HeaderSanitizers, QuerySanitizers, BodySanitizers. +// Note: Sanitized values cannot be exactly matched. If the configuration of sanitizers is changed, make sure +// to configure fuzzy matching accordingly. +// See NewRecordMatcher, FuzzyHeaders, FuzzyQueries, FuzzyForm and FuzzyJsonPaths func SanitizingHook() func(i *cassette.Interaction) error { reqJsonPaths := parseJsonPaths(FuzzyRequestJsonPaths.Values()) respJsonPaths := parseJsonPaths(FuzzyResponseJsonPaths.Values()) diff --git a/test/ittest/httpvcr_test.go b/test/ittest/httpvcr_test.go index 92785c98..fa2235e3 100644 --- a/test/ittest/httpvcr_test.go +++ b/test/ittest/httpvcr_test.go @@ -17,24 +17,23 @@ package ittest import ( - "context" - "fmt" - "github.com/cisco-open/go-lanai/pkg/utils" - "github.com/cisco-open/go-lanai/pkg/web" - "github.com/cisco-open/go-lanai/pkg/web/rest" - "github.com/cisco-open/go-lanai/test" - "github.com/cisco-open/go-lanai/test/apptest" - "github.com/cisco-open/go-lanai/test/webtest" - "github.com/onsi/gomega" - . "github.com/onsi/gomega" - "go.uber.org/fx" - "gopkg.in/dnaeon/go-vcr.v3/recorder" - "io" - "net/http" - "os" - "strings" - "testing" - "time" + "context" + "fmt" + "github.com/cisco-open/go-lanai/pkg/utils" + "github.com/cisco-open/go-lanai/pkg/web" + "github.com/cisco-open/go-lanai/pkg/web/rest" + "github.com/cisco-open/go-lanai/test" + "github.com/cisco-open/go-lanai/test/apptest" + "github.com/cisco-open/go-lanai/test/webtest" + "github.com/onsi/gomega" + . "github.com/onsi/gomega" + "gopkg.in/dnaeon/go-vcr.v3/recorder" + "io" + "net/http" + "os" + "strings" + "testing" + "time" ) /************************* @@ -43,6 +42,7 @@ import ( const ( RecordName = `HttpVCRTestRecords` + RecordAltName = `HttpVCRTestMoreRecords` PathGet = `/knock` PathPost = `/knock` RequiredQuery = "important" @@ -107,14 +107,8 @@ func (c TestController) Post(_ context.Context, r *TestRequest) (*TestResponse, Tests *************************/ -type vcrTestDI struct { - fx.In - Recorder *recorder.Recorder -} - func TestHttpVCRRecording(t *testing.T) { - var di vcrTestDI - t.Name() + var di RecorderDI test.RunTest(context.Background(), t, apptest.Bootstrap(), webtest.WithRealServer(), @@ -124,87 +118,126 @@ func TestHttpVCRRecording(t *testing.T) { apptest.WithFxOptions( web.FxControllerProviders(NewTestController), ), + test.GomegaSubTest(SubTestDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(true), "TestHttpVCRMode"), - test.GomegaSubTest(SubTestNormalGet(&di), "TestHttpVCRRecordGet"), - test.GomegaSubTest(SubTestNormalPostJson(&di), "TestHttpVCRRecordPostJson"), - test.GomegaSubTest(SubTestNormalPostForm(&di), "TestHttpVCRRecordPostForm"), + test.GomegaSubTest(SubTestNormalGet(), "TestHttpVCRRecordGet"), + test.GomegaSubTest(SubTestNormalPostJson(), "TestHttpVCRRecordPostJson"), + test.GomegaSubTest(SubTestNormalPostForm(), "TestHttpVCRRecordPostForm"), ) } func TestHttpVCRPlaybackExact(t *testing.T) { - var di vcrTestDI - t.Name() + var di RecorderDI test.RunTest(context.Background(), t, apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), + test.GomegaSubTest(SubTestDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), - test.GomegaSubTest(SubTestNormalGet(&di), "TestNormalGet"), - test.GomegaSubTest(SubTestNormalPostJson(&di), "TestNormalPostJson"), - test.GomegaSubTest(SubTestNormalPostForm(&di), "TestNormalPostForm"), + test.GomegaSubTest(SubTestNormalGet(), "TestNormalGet"), + test.GomegaSubTest(SubTestNormalPostJson(), "TestNormalPostJson"), + test.GomegaSubTest(SubTestNormalPostForm(), "TestNormalPostForm"), ) } func TestHttpVCRPlaybackEquivalent(t *testing.T) { - var di vcrTestDI - t.Name() + var di RecorderDI test.RunTest(context.Background(), t, apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), + test.GomegaSubTest(SubTestDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), - test.GomegaSubTest(SubTestEquivalentGet(&di), "TestEquivalentGet"), - test.GomegaSubTest(SubTestEquivalentPostJson(&di), "TestEquivalentPostJson"), - test.GomegaSubTest(SubTestEquivalentPostForm(&di), "TestEquivalentPostForm"), + test.GomegaSubTest(SubTestEquivalentGet(), "TestEquivalentGet"), + test.GomegaSubTest(SubTestEquivalentPostJson(), "TestEquivalentPostJson"), + test.GomegaSubTest(SubTestEquivalentPostForm(), "TestEquivalentPostForm"), ) } func TestHttpVCRPlaybackIncorrectOrder(t *testing.T) { - var di vcrTestDI - t.Name() + var di RecorderDI test.RunTest(context.Background(), t, apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), + test.GomegaSubTest(SubTestDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), - test.GomegaSubTest(SubTestDifferentRequestOrder(&di, false), "TestDifferentRequestOrder"), + test.GomegaSubTest(SubTestDifferentRequestOrder(false), "TestDifferentRequestOrder"), ) } func TestHttpVCRPlaybackWithOrderDisabled(t *testing.T) { - var di vcrTestDI - t.Name() + var di RecorderDI test.RunTest(context.Background(), t, apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost(), DisableHttpRecordOrdering()), apptest.WithDI(&di), + test.GomegaSubTest(SubTestDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), - test.GomegaSubTest(SubTestDifferentRequestOrder(&di, true), "TestDifferentRequestOrder"), + test.GomegaSubTest(SubTestDifferentRequestOrder(true), "TestDifferentRequestOrder"), ) } func TestHttpVCRPlaybackIncorrectQuery(t *testing.T) { - var di vcrTestDI - t.Name() + var di RecorderDI test.RunTest(context.Background(), t, apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), + test.GomegaSubTest(SubTestDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), - test.GomegaSubTest(SubTestIncorrectRequestQuery(&di), "TestIncorrectRequestQuery"), + test.GomegaSubTest(SubTestIncorrectRequestQuery(), "TestIncorrectRequestQuery"), ) } func TestHttpVCRPlaybackIncorrectBody(t *testing.T) { - var di vcrTestDI - t.Name() + var di RecorderDI test.RunTest(context.Background(), t, apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), + test.GomegaSubTest(SubTestDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), - test.GomegaSubTest(SubTestIncorrectRequestJsonBody(&di), "TestIncorrectRequestJsonBody"), - test.GomegaSubTest(SubTestIncorrectRequestFormBody(&di), "TestIncorrectRequestFormBody"), + test.GomegaSubTest(SubTestIncorrectRequestJsonBody(), "TestIncorrectRequestJsonBody"), + test.GomegaSubTest(SubTestIncorrectRequestFormBody(), "TestIncorrectRequestFormBody"), + ) +} + +// TestHttpVCRRecordingAltUsage test alternative usage by using NewHttpRecorder directly +func TestHttpVCRRecordingAltUsage(t *testing.T) { + test.RunTest(context.Background(), t, + apptest.Bootstrap(), + webtest.WithRealServer(), + test.Setup(SetupVCR( + HttpRecordingMode(), + HttpRecordName(RecordAltName), + HttpRecorderHooks(NewRecorderHookWithOrder(LocalhostRewriteHook(), recorder.BeforeSaveHook, 0)), + FixedHttpRecordDuration(DefaultHTTPDuration), + HttpTransport(http.DefaultTransport), + )), + test.Teardown(TeardownVCR()), + apptest.WithFxOptions( + web.FxControllerProviders(NewTestController), + ), + test.GomegaSubTest(SubTestHttpVCRMode(true), "TestHttpVCRMode"), + test.GomegaSubTest(SubTestNormalGet(), "TestHttpVCRRecordGet"), + test.GomegaSubTest(SubTestNormalPostJson(), "TestHttpVCRRecordPostJson"), + test.GomegaSubTest(SubTestNormalPostForm(), "TestHttpVCRRecordPostForm"), + ) +} + +func TestHttpVCRPlaybackAltUsage(t *testing.T) { + test.RunTest(context.Background(), t, + test.Setup(SetupVCR( + HttpRecordName(RecordAltName), + HttpRecordIgnoreHost(), + ApplyHttpLatency(), + )), + test.Teardown(TeardownVCR()), + test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), + test.GomegaSubTest(SubTestNormalGet(), "TestNormalGet"), + test.GomegaSubTest(SubTestNormalPostJson(), "TestNormalPostJson"), + test.GomegaSubTest(SubTestNormalPostForm(), "TestNormalPostForm"), ) } @@ -218,15 +251,38 @@ func TestHttpVCRRecordsConversion(t *testing.T) { Sub Tests *************************/ +func SetupVCR(opts ...HTTPVCROptions) test.SetupFunc { + return func(ctx context.Context, t *testing.T) (context.Context, error) { + opts = append([]HTTPVCROptions{HttpRecordName(t.Name())}, opts...) + return ContextWithNewHttpRecorder(ctx, opts...) + } +} + +func TeardownVCR() test.TeardownFunc { + return func(ctx context.Context, t *testing.T) error { + return StopRecorder(ctx) + } +} + +func SubTestDI(di *RecorderDI) test.GomegaSubTestFunc { + return func(ctx context.Context, t *testing.T, g *gomega.WithT) { + g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(di.RecorderOption).To(Not(BeZero()), "RecorderOption should be injected") + g.Expect(di.RecorderMatcher).To(Not(BeZero()), "RecorderMatcher should be injected") + g.Expect(di.HTTPVCROption).To(Not(BeZero()), "HTTPVCROption should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") + } +} + func SubTestHttpVCRMode(expectRecording bool) test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { g.Expect(IsRecording(ctx)).To(Equal(expectRecording), "mode should be correct") } } -func SubTestNormalGet(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestNormalGet() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") req := newGetRequest(ctx, t, g) resp, e := Client(ctx).Do(req) g.Expect(e).To(Succeed(), "sending request should succeed") @@ -234,9 +290,9 @@ func SubTestNormalGet(di *vcrTestDI) test.GomegaSubTestFunc { } } -func SubTestNormalPostJson(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestNormalPostJson() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") req := newPostJsonRequest(ctx, t, g) resp, e := Client(ctx).Do(req) g.Expect(e).To(Succeed(), "sending request should succeed") @@ -245,9 +301,9 @@ func SubTestNormalPostJson(di *vcrTestDI) test.GomegaSubTestFunc { } } -func SubTestNormalPostForm(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestNormalPostForm() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") req := newPostFormRequest(ctx, t, g) resp, e := Client(ctx).Do(req) @@ -256,9 +312,9 @@ func SubTestNormalPostForm(di *vcrTestDI) test.GomegaSubTestFunc { } } -func SubTestEquivalentGet(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestEquivalentGet() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") req := newGetRequest(ctx, t, g) resp, e := Client(ctx).Do(req) g.Expect(e).To(Succeed(), "sending request should succeed") @@ -266,9 +322,9 @@ func SubTestEquivalentGet(di *vcrTestDI) test.GomegaSubTestFunc { } } -func SubTestEquivalentPostJson(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestEquivalentPostJson() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") AdditionalMatcherOptions(ctx, FuzzyJsonPaths("$.time")) req := newPostJsonRequest(ctx, t, g, withBody(CorrectRequestJsonBodyAlt)) @@ -279,9 +335,9 @@ func SubTestEquivalentPostJson(di *vcrTestDI) test.GomegaSubTestFunc { } } -func SubTestEquivalentPostForm(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestEquivalentPostForm() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") AdditionalMatcherOptions(ctx, FuzzyForm("time")) req := newPostFormRequest(ctx, t, g, withBody(CorrectRequestFormBodyAlt)) @@ -291,9 +347,9 @@ func SubTestEquivalentPostForm(di *vcrTestDI) test.GomegaSubTestFunc { } } -func SubTestDifferentRequestOrder(di *vcrTestDI, expectSuccess bool) test.GomegaSubTestFunc { +func SubTestDifferentRequestOrder(expectSuccess bool) test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") var req *http.Request var resp *http.Response var e error @@ -318,9 +374,9 @@ func SubTestDifferentRequestOrder(di *vcrTestDI, expectSuccess bool) test.Gomega } } -func SubTestIncorrectRequestQuery(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestIncorrectRequestQuery() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") var req *http.Request var e error @@ -334,18 +390,18 @@ func SubTestIncorrectRequestQuery(di *vcrTestDI) test.GomegaSubTestFunc { } } -func SubTestIncorrectRequestJsonBody(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestIncorrectRequestJsonBody() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") req := newPostJsonRequest(ctx, t, g, withBody(IncorrectRequestJsonBody)) _, e := Client(ctx).Do(req) g.Expect(e).To(HaveOccurred(), "sending request with wrong body should fail") } } -func SubTestIncorrectRequestFormBody(di *vcrTestDI) test.GomegaSubTestFunc { +func SubTestIncorrectRequestFormBody() test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { - g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") + g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") req := newPostFormRequest(ctx, t, g, withBody(IncorrectRequestFormBody)) _, e := Client(ctx).Do(req) @@ -426,7 +482,7 @@ func newPostFormRequest(ctx context.Context, _ *testing.T, g *gomega.WithT, opts func prepareRequest(req *http.Request, contentType string, opts []webtest.RequestOptions) { // set headers - for k := range FuzzyRequestHeaders { + for k := range FuzzyRequestHeaders { req.Header.Set(k, utils.RandomString(20)) } req.Header.Set("Content-Type", contentType) diff --git a/test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml b/test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml new file mode 100644 index 00000000..6eda4612 --- /dev/null +++ b/test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml @@ -0,0 +1,137 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: webservice + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Authorization: + - OvYGcxDI7ieChSQnQ0tP + Content-Type: + - application/x-www-form-urlencoded; charset=utf-8 + X-Http-Record-Index: + - "0" + url: http://webservice/test/knock?access_token=ExzoS2H1im&important=value+should+match&nonce=emYqjG2mzw&password=iNYFfKTMNZ&secret=axKs0OHAPS&token=lYJhEy8R9B + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 223 + uncompressed: false + body: | + {"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z","slice":[{"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z"}],"object":{"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z"}} + headers: + Content-Length: + - "223" + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 21 Jun 2024 19:55:14 GMT + status: 200 OK + code: 200 + duration: 200µs + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 73 + transfer_encoding: [] + trailer: {} + host: webservice + remote_addr: "" + request_uri: "" + body: '{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}' + form: {} + headers: + Authorization: + - rUG8JktXfuS0yHDTrkv1 + Content-Type: + - application/json; charset=utf-8 + X-Http-Record-Index: + - "1" + url: http://webservice/test/knock?access_token=IBXcWIXvxP&important=value+should+match&nonce=27Fsydqbtj&password=VypLKZvIq2&secret=DHLe3mOoC2&token=iuM2rdIFpB + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 241 + uncompressed: false + body: | + {"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z","slice":[{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}],"object":{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}} + headers: + Content-Length: + - "241" + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 21 Jun 2024 19:55:14 GMT + status: 200 OK + code: 200 + duration: 200µs + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 63 + transfer_encoding: [] + trailer: {} + host: webservice + remote_addr: "" + request_uri: "" + body: string=correct&number=1&bool=true&time=2022-10-11T00%3A00%3A00Z + form: + bool: + - "true" + number: + - "1" + string: + - correct + time: + - "2022-10-11T00:00:00Z" + headers: + Authorization: + - Iz1Gp24bPgd4SL6w2qRS + Content-Type: + - application/x-www-form-urlencoded; charset=utf-8 + X-Http-Record-Index: + - "2" + url: http://webservice/test/knock?access_token=MT9nxmRxn8&important=value+should+match&nonce=Bm2Q35SfDd&password=nnMvCCkdcc&secret=GjKhBPbTVh&token=K0QLNvCRnj + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 241 + uncompressed: false + body: | + {"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z","slice":[{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}],"object":{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}} + headers: + Content-Length: + - "241" + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 21 Jun 2024 19:55:14 GMT + status: 200 OK + code: 200 + duration: 200µs diff --git a/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml b/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml index 1da13f44..c75d5d51 100644 --- a/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml +++ b/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml @@ -41,7 +41,7 @@ interactions: - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 - duration: 200µs + duration: 26.538791ms - id: 1 request: proto: HTTP/1.1 @@ -82,7 +82,7 @@ interactions: - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 - duration: 200µs + duration: 374.333µs - id: 2 request: proto: HTTP/1.1 @@ -131,4 +131,4 @@ interactions: - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 - duration: 200µs + duration: 192.791µs diff --git a/test/opensearchtest/recorder.go b/test/opensearchtest/recorder.go index 1291068f..f06a9b38 100644 --- a/test/opensearchtest/recorder.go +++ b/test/opensearchtest/recorder.go @@ -49,7 +49,7 @@ func NewRecorder(options ...Options) (*recorder.Recorder, error) { if recordOption.Name == "" { return nil, ErrNoCassetteName } - rec, e := ittest.NewHttpRecorder(toHTTPVCROptions(recordOption)) + rec, e := ittest.NewHttpRecorder(ittest.HttpRecordOrdering(false), toHTTPVCROptions(recordOption)) if e != nil { return nil, fmt.Errorf("%w, %v", ErrCreatingRecorder, e) } From c2b84b6543dcdcf41fa8e1790eb48bd138943bf4 Mon Sep 17 00:00:00 2001 From: Livan Du Date: Fri, 21 Jun 2024 17:12:23 -0400 Subject: [PATCH 2/4] updated ittest to avoid records changes on each test run --- test/ittest/httpvcr_test.go | 6 +++- .../HttpVCRTestMoreRecords.httpvcr.yaml | 29 +++++++++---------- .../testdata/HttpVCRTestRecords.httpvcr.yaml | 6 ++-- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/test/ittest/httpvcr_test.go b/test/ittest/httpvcr_test.go index fa2235e3..17901afb 100644 --- a/test/ittest/httpvcr_test.go +++ b/test/ittest/httpvcr_test.go @@ -113,7 +113,10 @@ func TestHttpVCRRecording(t *testing.T) { apptest.Bootstrap(), webtest.WithRealServer(), WithHttpPlayback(t, HttpRecordName(RecordName), - HttpRecordingMode(), HttpRecorderHooks(NewRecorderHookWithOrder(LocalhostRewriteHook(), recorder.BeforeSaveHook, 0))), + HttpRecordingMode(), + FixedHttpRecordDuration(DefaultHTTPDuration), + HttpRecorderHooks(NewRecorderHookWithOrder(LocalhostRewriteHook(), recorder.BeforeSaveHook, 0)), + ), apptest.WithDI(&di), apptest.WithFxOptions( web.FxControllerProviders(NewTestController), @@ -212,6 +215,7 @@ func TestHttpVCRRecordingAltUsage(t *testing.T) { HttpRecordingMode(), HttpRecordName(RecordAltName), HttpRecorderHooks(NewRecorderHookWithOrder(LocalhostRewriteHook(), recorder.BeforeSaveHook, 0)), + SanitizeHttpRecord(), FixedHttpRecordDuration(DefaultHTTPDuration), HttpTransport(http.DefaultTransport), )), diff --git a/test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml b/test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml index 6eda4612..1da13f44 100644 --- a/test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml +++ b/test/ittest/testdata/HttpVCRTestMoreRecords.httpvcr.yaml @@ -16,12 +16,12 @@ interactions: form: {} headers: Authorization: - - OvYGcxDI7ieChSQnQ0tP + - '******' Content-Type: - application/x-www-form-urlencoded; charset=utf-8 X-Http-Record-Index: - "0" - url: http://webservice/test/knock?access_token=ExzoS2H1im&important=value+should+match&nonce=emYqjG2mzw&password=iNYFfKTMNZ&secret=axKs0OHAPS&token=lYJhEy8R9B + url: http://webservice/test/knock?access_token=_hidden&important=value+should+match&nonce=_hidden&password=_hidden&secret=_hidden&token=_hidden method: GET response: proto: HTTP/1.1 @@ -31,15 +31,14 @@ interactions: trailer: {} content_length: 223 uncompressed: false - body: | - {"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z","slice":[{"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z"}],"object":{"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z"}} + body: '{"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z","slice":[{"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z"}],"object":{"string":"","number":0,"bool":false,"time":"0001-01-01T00:00:00Z"}}' headers: Content-Length: - "223" Content-Type: - application/json; charset=utf-8 Date: - - Fri, 21 Jun 2024 19:55:14 GMT + - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 duration: 200µs @@ -58,12 +57,12 @@ interactions: form: {} headers: Authorization: - - rUG8JktXfuS0yHDTrkv1 + - '******' Content-Type: - application/json; charset=utf-8 X-Http-Record-Index: - "1" - url: http://webservice/test/knock?access_token=IBXcWIXvxP&important=value+should+match&nonce=27Fsydqbtj&password=VypLKZvIq2&secret=DHLe3mOoC2&token=iuM2rdIFpB + url: http://webservice/test/knock?access_token=_hidden&important=value+should+match&nonce=_hidden&password=_hidden&secret=_hidden&token=_hidden method: POST response: proto: HTTP/1.1 @@ -73,15 +72,14 @@ interactions: trailer: {} content_length: 241 uncompressed: false - body: | - {"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z","slice":[{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}],"object":{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}} + body: '{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z","slice":[{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}],"object":{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}}' headers: Content-Length: - "241" Content-Type: - application/json; charset=utf-8 Date: - - Fri, 21 Jun 2024 19:55:14 GMT + - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 duration: 200µs @@ -96,7 +94,7 @@ interactions: host: webservice remote_addr: "" request_uri: "" - body: string=correct&number=1&bool=true&time=2022-10-11T00%3A00%3A00Z + body: bool=true&number=1&string=correct&time=2022-10-11T00%3A00%3A00Z form: bool: - "true" @@ -108,12 +106,12 @@ interactions: - "2022-10-11T00:00:00Z" headers: Authorization: - - Iz1Gp24bPgd4SL6w2qRS + - '******' Content-Type: - application/x-www-form-urlencoded; charset=utf-8 X-Http-Record-Index: - "2" - url: http://webservice/test/knock?access_token=MT9nxmRxn8&important=value+should+match&nonce=Bm2Q35SfDd&password=nnMvCCkdcc&secret=GjKhBPbTVh&token=K0QLNvCRnj + url: http://webservice/test/knock?access_token=_hidden&important=value+should+match&nonce=_hidden&password=_hidden&secret=_hidden&token=_hidden method: POST response: proto: HTTP/1.1 @@ -123,15 +121,14 @@ interactions: trailer: {} content_length: 241 uncompressed: false - body: | - {"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z","slice":[{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}],"object":{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}} + body: '{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z","slice":[{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}],"object":{"string":"correct","number":1,"bool":true,"time":"2022-10-11T00:00:00Z"}}' headers: Content-Length: - "241" Content-Type: - application/json; charset=utf-8 Date: - - Fri, 21 Jun 2024 19:55:14 GMT + - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 duration: 200µs diff --git a/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml b/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml index c75d5d51..1da13f44 100644 --- a/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml +++ b/test/ittest/testdata/HttpVCRTestRecords.httpvcr.yaml @@ -41,7 +41,7 @@ interactions: - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 - duration: 26.538791ms + duration: 200µs - id: 1 request: proto: HTTP/1.1 @@ -82,7 +82,7 @@ interactions: - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 - duration: 374.333µs + duration: 200µs - id: 2 request: proto: HTTP/1.1 @@ -131,4 +131,4 @@ interactions: - Fri, 19 Aug 2022 8:51:32 GMT status: 200 OK code: 200 - duration: 192.791µs + duration: 200µs From 6fb4fb895428284aa063891cf8ea8e312c98fda1 Mon Sep 17 00:00:00 2001 From: Livan Du Date: Mon, 24 Jun 2024 12:43:03 -0400 Subject: [PATCH 3/4] updated ittest recorder hook interface to support hook removal --- test/ittest/context.go | 1 + test/ittest/httpvcr.go | 41 +++++++++++++++++++----- test/ittest/httpvcr_hooks.go | 62 ++++++++++++++++++++++++------------ test/ittest/httpvcr_test.go | 47 ++++++++++++++++++++------- test/ittest/integration.go | 7 ++-- 5 files changed, 114 insertions(+), 44 deletions(-) diff --git a/test/ittest/context.go b/test/ittest/context.go index e7c36c02..12919d27 100644 --- a/test/ittest/context.go +++ b/test/ittest/context.go @@ -93,6 +93,7 @@ type RecordBodyMatcher interface { // RecorderHook wrapper of recorder.Hook type RecorderHook interface { + Name() string Handler() recorder.HookFunc Kind() recorder.HookKind } diff --git a/test/ittest/httpvcr.go b/test/ittest/httpvcr.go index 7b4fc84c..f2ac2c64 100644 --- a/test/ittest/httpvcr.go +++ b/test/ittest/httpvcr.go @@ -65,6 +65,7 @@ func WithHttpPlayback(t *testing.T, opts ...HTTPVCROptions) test.Options { opts = append([]HTTPVCROptions{ HttpRecordName(t.Name()), SanitizeHttpRecord(), + FixedHttpRecordDuration(DefaultHTTPDuration), }, opts...) var di recorderDI @@ -206,7 +207,32 @@ func HttpRecordMatching(opts ...RecordMatcherOptions) HTTPVCROptions { // the order will be respected func HttpRecorderHooks(hooks ...RecorderHook) HTTPVCROptions { return func(opt *HTTPVCROption) { - opt.Hooks = append(opt.Hooks, hooks...) +LOOP: + for i := range hooks { + for j := range opt.Hooks { + if hooks[i].Name() == opt.Hooks[j].Name() { + opt.Hooks[j] = hooks[i] + continue LOOP + } + } + opt.Hooks = append(opt.Hooks, hooks[i]) + } + } +} + +// DisableHttpRecorderHooks returns a HTTPVCROptions that removes installed hooks by name +func DisableHttpRecorderHooks(names ...string) HTTPVCROptions { + return func(opt *HTTPVCROption) { + LOOP: + for i := range names { + for j := range opt.Hooks { + if names[i] == opt.Hooks[j].Name() { + opt.Hooks[j] = opt.Hooks[len(opt.Hooks)-1] + opt.Hooks = opt.Hooks[:len(opt.Hooks)-1] + continue LOOP + } + } + } } } @@ -254,19 +280,18 @@ func ApplyHttpLatency() HTTPVCROptions { // SanitizeHttpRecord install a hook to sanitize request and response before they are saved in file. // See SanitizingHook for details. func SanitizeHttpRecord() HTTPVCROptions { - return func(opt *HTTPVCROption) { - opt.Hooks = append(opt.Hooks, NewRecorderHook(SanitizingHook(), recorder.BeforeSaveHook)) - } + return HttpRecorderHooks(SanitizingHook()) } // FixedHttpRecordDuration install a hook to set a fixed duration on interactions before they are saved. -// Otherwise, the actual latency will be recorded. +// If the duration is less or equal to 0, the actual latency will be recorded. // When HTTPVCROption.SkipRequestLatency is set to false, the recorded duration will be applied during playback // See FixedDurationHook for details. func FixedHttpRecordDuration(duration time.Duration) HTTPVCROptions { - return func(opt *HTTPVCROption) { - opt.Hooks = append(opt.Hooks, NewRecorderHook(FixedDurationHook(duration), recorder.BeforeSaveHook)) + if duration <= 0 { + return DisableHttpRecorderHooks(HookNameFixedDuration) } + return HttpRecorderHooks(FixedDurationHook(duration)) } /**************************** @@ -346,7 +371,7 @@ func NewHttpRecorder(opts ...HTTPVCROptions) (*HttpRecorder, error) { opt := HTTPVCROption{ SavePath: "testdata", Hooks: []RecorderHook{ - NewRecorderHook(InteractionIndexAwareHook(), recorder.BeforeSaveHook), + InteractionIndexAwareHook(), }, SkipRequestLatency: true, indexAwareWrapper: newIndexAwareMatcherWrapper(), // enforce order diff --git a/test/ittest/httpvcr_hooks.go b/test/ittest/httpvcr_hooks.go index d3419212..92567b0b 100644 --- a/test/ittest/httpvcr_hooks.go +++ b/test/ittest/httpvcr_hooks.go @@ -34,12 +34,20 @@ const ( DefaultHost = "webservice" ) +const ( + HookNameIndexAware = "index-aware" + HookNameSanitize = "sanitize" + HookNameFixedDuration = "fixed-duration" + HookNameLocalhostRewrite = "localhost-rewrite" +) + /************************ Common ************************/ -func NewRecorderHook(fn recorder.HookFunc, kind recorder.HookKind) RecorderHook { +func NewRecorderHook(name string, fn recorder.HookFunc, kind recorder.HookKind) RecorderHook { return recorderHook{ + name: name, hook: recorder.Hook{ Handler: fn, Kind: kind, @@ -48,20 +56,26 @@ func NewRecorderHook(fn recorder.HookFunc, kind recorder.HookKind) RecorderHook } type recorderHook struct { + name string hook recorder.Hook } -func (w recorderHook) Handler() recorder.HookFunc { - return w.hook.Handler +func (h recorderHook) Name() string { + return h.name } -func (w recorderHook) Kind() recorder.HookKind { - return w.hook.Kind +func (h recorderHook) Handler() recorder.HookFunc { + return h.hook.Handler } -func NewRecorderHookWithOrder(fn recorder.HookFunc, kind recorder.HookKind, order int) RecorderHook { +func (h recorderHook) Kind() recorder.HookKind { + return h.hook.Kind +} + +func NewRecorderHookWithOrder(name string, fn recorder.HookFunc, kind recorder.HookKind, order int) RecorderHook { return orderedRecorderHook{ recorderHook: recorderHook{ + name: name, hook: recorder.Hook{ Handler: fn, Kind: kind, @@ -90,14 +104,14 @@ var ( "Date": SubstituteValueSanitizer("Fri, 19 Aug 2022 8:51:32 GMT"), } QuerySanitizers = map[string]ValueSanitizer{ - "password": DefaultValueSanitizer(), - "secret": DefaultValueSanitizer(), - "nonce": DefaultValueSanitizer(), - "token": DefaultValueSanitizer(), - "access_token": DefaultValueSanitizer(), + "password": DefaultValueSanitizer(), + "secret": DefaultValueSanitizer(), + "nonce": DefaultValueSanitizer(), + "token": DefaultValueSanitizer(), + "access_token": DefaultValueSanitizer(), } BodySanitizers = map[string]ValueSanitizer{ - "access_token": DefaultValueSanitizer(), + "access_token": DefaultValueSanitizer(), } ) @@ -132,22 +146,25 @@ func DefaultValueSanitizer() ValueSanitizer { // InteractionIndexAwareHook inject interaction index into stored header: // httpvcr store interaction's ID but doesn't expose it to cassette.MatchFunc, // so we need to store it in request for request matchers to access -func InteractionIndexAwareHook() func(i *cassette.Interaction) error { - return func(i *cassette.Interaction) error { +func InteractionIndexAwareHook() RecorderHook { + fn := func(i *cassette.Interaction) error { i.Request.Headers.Set(xInteractionIndexHeader, strconv.Itoa(i.ID)) return nil } + return NewRecorderHook(HookNameIndexAware, fn, recorder.BeforeSaveHook) } // SanitizingHook is an HTTP VCR hook that sanitize values in header, query, body (x-form-urlencoded/json). // Values to sanitize are globally configured via HeaderSanitizers, QuerySanitizers, BodySanitizers. // Note: Sanitized values cannot be exactly matched. If the configuration of sanitizers is changed, make sure -// to configure fuzzy matching accordingly. +// +// to configure fuzzy matching accordingly. +// // See NewRecordMatcher, FuzzyHeaders, FuzzyQueries, FuzzyForm and FuzzyJsonPaths -func SanitizingHook() func(i *cassette.Interaction) error { +func SanitizingHook() RecorderHook { reqJsonPaths := parseJsonPaths(FuzzyRequestJsonPaths.Values()) respJsonPaths := parseJsonPaths(FuzzyResponseJsonPaths.Values()) - return func(i *cassette.Interaction) error { + fn := func(i *cassette.Interaction) error { i.Request.Headers = sanitizeHeaders(i.Request.Headers, FuzzyRequestHeaders) i.Request.URL = sanitizeUrl(i.Request.URL, FuzzyRequestQueries) switch mediaType(i.Request.Headers) { @@ -164,11 +181,12 @@ func SanitizingHook() func(i *cassette.Interaction) error { } return nil } + return NewRecorderHookWithOrder(HookNameSanitize, fn, recorder.BeforeSaveHook, 0) } // LocalhostRewriteHook changes the host of request to a pre-defined constant if it is localhost, in order to avoid randomness -func LocalhostRewriteHook() func(i *cassette.Interaction) error { - return func(i *cassette.Interaction) error { +func LocalhostRewriteHook() RecorderHook { + fn := func(i *cassette.Interaction) error { if strings.HasPrefix(i.Request.Host, "localhost") || strings.HasPrefix(i.Request.Host, "127.0.0.1") { i.Request.URL = strings.Replace(i.Request.URL, i.Request.Host, DefaultHost, 1) i.Request.Host = DefaultHost @@ -176,14 +194,16 @@ func LocalhostRewriteHook() func(i *cassette.Interaction) error { return nil } + return NewRecorderHook(HookNameLocalhostRewrite, fn, recorder.BeforeSaveHook) } // FixedDurationHook changes the duration of record HTTP interaction to constant, to avoid randomness -func FixedDurationHook(duration time.Duration) func(i *cassette.Interaction) error { - return func(i *cassette.Interaction) error { +func FixedDurationHook(duration time.Duration) RecorderHook { + fn := func(i *cassette.Interaction) error { i.Response.Duration = duration return nil } + return NewRecorderHook(HookNameFixedDuration, fn, recorder.BeforeSaveHook) } /************************ diff --git a/test/ittest/httpvcr_test.go b/test/ittest/httpvcr_test.go index 17901afb..8b04bcff 100644 --- a/test/ittest/httpvcr_test.go +++ b/test/ittest/httpvcr_test.go @@ -114,14 +114,13 @@ func TestHttpVCRRecording(t *testing.T) { webtest.WithRealServer(), WithHttpPlayback(t, HttpRecordName(RecordName), HttpRecordingMode(), - FixedHttpRecordDuration(DefaultHTTPDuration), - HttpRecorderHooks(NewRecorderHookWithOrder(LocalhostRewriteHook(), recorder.BeforeSaveHook, 0)), - ), + HttpRecorderHooks(LocalhostRewriteHook()), + ), apptest.WithDI(&di), apptest.WithFxOptions( web.FxControllerProviders(NewTestController), ), - test.GomegaSubTest(SubTestDI(&di), "TestDI"), + test.GomegaSubTest(SubTestVcrDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(true), "TestHttpVCRMode"), test.GomegaSubTest(SubTestNormalGet(), "TestHttpVCRRecordGet"), test.GomegaSubTest(SubTestNormalPostJson(), "TestHttpVCRRecordPostJson"), @@ -135,7 +134,7 @@ func TestHttpVCRPlaybackExact(t *testing.T) { apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), - test.GomegaSubTest(SubTestDI(&di), "TestDI"), + test.GomegaSubTest(SubTestVcrDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), test.GomegaSubTest(SubTestNormalGet(), "TestNormalGet"), test.GomegaSubTest(SubTestNormalPostJson(), "TestNormalPostJson"), @@ -149,7 +148,7 @@ func TestHttpVCRPlaybackEquivalent(t *testing.T) { apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), - test.GomegaSubTest(SubTestDI(&di), "TestDI"), + test.GomegaSubTest(SubTestVcrDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), test.GomegaSubTest(SubTestEquivalentGet(), "TestEquivalentGet"), test.GomegaSubTest(SubTestEquivalentPostJson(), "TestEquivalentPostJson"), @@ -163,7 +162,7 @@ func TestHttpVCRPlaybackIncorrectOrder(t *testing.T) { apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), - test.GomegaSubTest(SubTestDI(&di), "TestDI"), + test.GomegaSubTest(SubTestVcrDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), test.GomegaSubTest(SubTestDifferentRequestOrder(false), "TestDifferentRequestOrder"), ) @@ -175,7 +174,7 @@ func TestHttpVCRPlaybackWithOrderDisabled(t *testing.T) { apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost(), DisableHttpRecordOrdering()), apptest.WithDI(&di), - test.GomegaSubTest(SubTestDI(&di), "TestDI"), + test.GomegaSubTest(SubTestVcrDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), test.GomegaSubTest(SubTestDifferentRequestOrder(true), "TestDifferentRequestOrder"), ) @@ -187,7 +186,7 @@ func TestHttpVCRPlaybackIncorrectQuery(t *testing.T) { apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), - test.GomegaSubTest(SubTestDI(&di), "TestDI"), + test.GomegaSubTest(SubTestVcrDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), test.GomegaSubTest(SubTestIncorrectRequestQuery(), "TestIncorrectRequestQuery"), ) @@ -199,7 +198,7 @@ func TestHttpVCRPlaybackIncorrectBody(t *testing.T) { apptest.Bootstrap(), WithHttpPlayback(t, HttpRecordName(RecordName), DisableHttpRecordingMode(), HttpRecordIgnoreHost()), apptest.WithDI(&di), - test.GomegaSubTest(SubTestDI(&di), "TestDI"), + test.GomegaSubTest(SubTestVcrDI(&di), "TestDI"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), test.GomegaSubTest(SubTestIncorrectRequestJsonBody(), "TestIncorrectRequestJsonBody"), test.GomegaSubTest(SubTestIncorrectRequestFormBody(), "TestIncorrectRequestFormBody"), @@ -214,15 +213,18 @@ func TestHttpVCRRecordingAltUsage(t *testing.T) { test.Setup(SetupVCR( HttpRecordingMode(), HttpRecordName(RecordAltName), - HttpRecorderHooks(NewRecorderHookWithOrder(LocalhostRewriteHook(), recorder.BeforeSaveHook, 0)), + HttpRecorderHooks(LocalhostRewriteHook()), SanitizeHttpRecord(), FixedHttpRecordDuration(DefaultHTTPDuration), + FixedHttpRecordDuration(0), + FixedHttpRecordDuration(DefaultHTTPDuration), HttpTransport(http.DefaultTransport), )), test.Teardown(TeardownVCR()), apptest.WithFxOptions( web.FxControllerProviders(NewTestController), ), + test.GomegaSubTest(SubTestVcrContext(), "TestVcrContext"), test.GomegaSubTest(SubTestHttpVCRMode(true), "TestHttpVCRMode"), test.GomegaSubTest(SubTestNormalGet(), "TestHttpVCRRecordGet"), test.GomegaSubTest(SubTestNormalPostJson(), "TestHttpVCRRecordPostJson"), @@ -238,6 +240,7 @@ func TestHttpVCRPlaybackAltUsage(t *testing.T) { ApplyHttpLatency(), )), test.Teardown(TeardownVCR()), + test.GomegaSubTest(SubTestVcrContext(), "TestVcrContext"), test.GomegaSubTest(SubTestHttpVCRMode(false), "TestHttpVCRMode"), test.GomegaSubTest(SubTestNormalGet(), "TestNormalGet"), test.GomegaSubTest(SubTestNormalPostJson(), "TestNormalPostJson"), @@ -268,13 +271,33 @@ func TeardownVCR() test.TeardownFunc { } } -func SubTestDI(di *RecorderDI) test.GomegaSubTestFunc { +func SubTestVcrDI(di *RecorderDI) test.GomegaSubTestFunc { return func(ctx context.Context, t *testing.T, g *gomega.WithT) { g.Expect(di.Recorder).To(Not(BeNil()), "Recorder should be injected") g.Expect(di.RecorderOption).To(Not(BeZero()), "RecorderOption should be injected") g.Expect(di.RecorderMatcher).To(Not(BeZero()), "RecorderMatcher should be injected") g.Expect(di.HTTPVCROption).To(Not(BeZero()), "HTTPVCROption should be injected") g.Expect(Recorder(ctx)).To(Not(BeNil()), "Recorder from context should be available") + if IsRecording(ctx) { + g.Expect(di.HTTPVCROption.Hooks).To(HaveLen(4), "HTTPVCROption.Hooks should have correct length") + } else { + g.Expect(di.HTTPVCROption.Hooks).To(HaveLen(3), "HTTPVCROption.Hooks should have correct length") + } + } +} + +func SubTestVcrContext() test.GomegaSubTestFunc { + return func(ctx context.Context, t *testing.T, g *gomega.WithT) { + rec := Recorder(ctx) + g.Expect(rec).To(Not(BeNil()), "Recorder from context should be available") + g.Expect(rec.RawOptions).To(Not(BeZero()), "RawOptions should be available") + g.Expect(rec.Matcher).To(Not(BeZero()), "Matcher should be available") + g.Expect(rec.Options).To(Not(BeZero()), "Options should be available") + if IsRecording(ctx) { + g.Expect(rec.Options.Hooks).To(HaveLen(4), "Options.Hooks should have correct length") + } else { + g.Expect(rec.Options.Hooks).To(HaveLen(1), "Options.Hooks should have correct length") + } } } diff --git a/test/ittest/integration.go b/test/ittest/integration.go index 0bfc12a4..419a0a76 100644 --- a/test/ittest/integration.go +++ b/test/ittest/integration.go @@ -93,7 +93,7 @@ type scopeVCROptionsOut struct { func provideScopeVCROptions() scopeVCROptionsOut { return scopeVCROptionsOut{ - VCROptions: HttpRecorderHooks(NewRecorderHook(extendedTokenValidityHook(), recorder.BeforeResponseReplayHook)), + VCROptions: HttpRecorderHooks(extendedTokenValidityHook()), } } @@ -105,7 +105,7 @@ func provideScopeVCROptions() scopeVCROptionsOut { // During scope switching, token's expiry time is used to determine if token need to be refreshed. // This would cause inconsistent HTTP interactions between recording time and replay time (after token expires) // "expiry" and "expires_in" are JSON fields in `/v2/token` response and `exp` is a standard claim in `/v2/check_token` response -func extendedTokenValidityHook() func(i *cassette.Interaction) error { +func extendedTokenValidityHook() RecorderHook { longValidity := 100 * 24 * 365 * time.Hour expiry := time.Now().Add(longValidity) tokenBodySanitizers := map[string]ValueSanitizer{ @@ -114,7 +114,7 @@ func extendedTokenValidityHook() func(i *cassette.Interaction) error { "exp": SubstituteValueSanitizer(expiry.Unix()), } tokenBodyJsonPaths := parseJsonPaths([]string{"$.expiry", "$.expires_in", "$.exp"}) - return func(i *cassette.Interaction) error { + fn := func(i *cassette.Interaction) error { if i.Response.Code != http.StatusOK || !strings.Contains(i.Request.URL, "/v2/token") && !strings.Contains(i.Request.URL, "/v2/check_token") { return nil @@ -122,4 +122,5 @@ func extendedTokenValidityHook() func(i *cassette.Interaction) error { i.Response.Body = sanitizeJsonBody(i.Response.Body, tokenBodySanitizers, tokenBodyJsonPaths) return nil } + return NewRecorderHook("extend-token-validity", fn, recorder.BeforeResponseReplayHook) } From 8938e3b3982ca35b47baaee595d5bce29dcb7b98 Mon Sep 17 00:00:00 2001 From: Livan Du Date: Mon, 8 Jul 2024 13:45:30 -0400 Subject: [PATCH 4/4] updated in response to the PR review comments --- test/ittest/httpvcr.go | 26 +++++++++++++++----------- test/ittest/httpvcr_hooks.go | 2 +- test/ittest/httpvcr_test.go | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/test/ittest/httpvcr.go b/test/ittest/httpvcr.go index f2ac2c64..fc70654c 100644 --- a/test/ittest/httpvcr.go +++ b/test/ittest/httpvcr.go @@ -130,15 +130,17 @@ func AdditionalMatcherOptions(ctx context.Context, opts ...RecordMatcherOptions) } // ResetRecorder revert the change made by AdditionalMatcherOptions. +// Note: If tests configured via WithHttpPlayback, this method is automatically invoked at sub-test teardown. func ResetRecorder(ctx context.Context) { rec, ok := ctx.Value(ckRecorder).(*HttpRecorder) if !ok || rec.Recorder == nil { return } - rec.SetMatcher(rec.Matcher) + rec.SetMatcher(rec.InitMatcher) } // StopRecorder stops the recorder extracted from the given context. +// Note: If tests configured via WithHttpPlayback, this method is automatically invoked at test teardown. func StopRecorder(ctx context.Context) error { rec, ok := ctx.Value(ckRecorder).(*HttpRecorder) if !ok || rec.Recorder == nil { @@ -270,7 +272,8 @@ func HttpTransport(transport http.RoundTripper) HTTPVCROptions { } } -// ApplyHttpLatency apply recorded HTTP latency. By default, HTTP latency is not applied for faster test run. This option has no effect in recording mode +// ApplyHttpLatency apply recorded HTTP latency. By default, HTTP latency is not applied for faster test run. +// This option has no effect in recording mode. func ApplyHttpLatency() HTTPVCROptions { return func(opt *HTTPVCROption) { opt.SkipRequestLatency = false @@ -285,8 +288,9 @@ func SanitizeHttpRecord() HTTPVCROptions { // FixedHttpRecordDuration install a hook to set a fixed duration on interactions before they are saved. // If the duration is less or equal to 0, the actual latency will be recorded. -// When HTTPVCROption.SkipRequestLatency is set to false, the recorded duration will be applied during playback +// When HTTPVCROption.SkipRequestLatency is set to false (via ApplyHttpLatency option), the recorded duration will be applied during playback // See FixedDurationHook for details. +// This option has no effect in playback mode. func FixedHttpRecordDuration(duration time.Duration) HTTPVCROptions { if duration <= 0 { return DisableHttpRecorderHooks(HookNameFixedDuration) @@ -348,9 +352,9 @@ func recorderReset() test.TeardownFunc { // Note: This type is for other test utilities to re-configure recorder.RawRecorder type HttpRecorder struct { *recorder.Recorder - RawOptions *recorder.Options - Matcher cassette.MatcherFunc - Options *HTTPVCROption + RawOptions *recorder.Options + InitMatcher cassette.MatcherFunc + Options *HTTPVCROption } // ContextWithNewHttpRecorder is a convenient function that create a new HTTP recorder and store it in context. @@ -395,10 +399,10 @@ func NewHttpRecorder(opts ...HTTPVCROptions) (*HttpRecorder, error) { rec.AddHook(h.Handler(), h.Kind()) } return &HttpRecorder{ - Recorder: rec, - RawOptions: rawOpts, - Matcher: matcher, - Options: &opt, + Recorder: rec, + RawOptions: rawOpts, + InitMatcher: matcher, + Options: &opt, }, nil } @@ -431,7 +435,7 @@ func httpRecorderProvider(opts []HTTPVCROptions) func(di vcrDI) (vcrOut, error) return vcrOut{ HTTPRecorder: rec, RawRecorder: rec.Recorder, - CassetteMatcher: rec.Matcher, + CassetteMatcher: rec.InitMatcher, HttpVCROption: rec.Options, RawRecorderOption: rec.RawOptions, HttpClientCustomizer: httpclient.ClientCustomizerFunc(func(opt *httpclient.ClientOption) { diff --git a/test/ittest/httpvcr_hooks.go b/test/ittest/httpvcr_hooks.go index 92567b0b..ce39acf7 100644 --- a/test/ittest/httpvcr_hooks.go +++ b/test/ittest/httpvcr_hooks.go @@ -144,7 +144,7 @@ func DefaultValueSanitizer() ValueSanitizer { ************************/ // InteractionIndexAwareHook inject interaction index into stored header: -// httpvcr store interaction's ID but doesn't expose it to cassette.MatchFunc, +// httpvcr store interaction's ID but doesn't expose it to cassette.MatcherFunc, // so we need to store it in request for request matchers to access func InteractionIndexAwareHook() RecorderHook { fn := func(i *cassette.Interaction) error { diff --git a/test/ittest/httpvcr_test.go b/test/ittest/httpvcr_test.go index 8b04bcff..2da9829a 100644 --- a/test/ittest/httpvcr_test.go +++ b/test/ittest/httpvcr_test.go @@ -291,7 +291,7 @@ func SubTestVcrContext() test.GomegaSubTestFunc { rec := Recorder(ctx) g.Expect(rec).To(Not(BeNil()), "Recorder from context should be available") g.Expect(rec.RawOptions).To(Not(BeZero()), "RawOptions should be available") - g.Expect(rec.Matcher).To(Not(BeZero()), "Matcher should be available") + g.Expect(rec.InitMatcher).To(Not(BeZero()), "InitMatcher should be available") g.Expect(rec.Options).To(Not(BeZero()), "Options should be available") if IsRecording(ctx) { g.Expect(rec.Options.Hooks).To(HaveLen(4), "Options.Hooks should have correct length")