-
-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add hook to allow roundtripping values #90
Comments
Hey @quad , Is it possible to provide a working code example which we can look at? Thanks! |
Here's a minimal example, using the package main
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/google/uuid"
"gopkg.in/dnaeon/go-vcr.v3/cassette"
"gopkg.in/dnaeon/go-vcr.v3/recorder"
)
func httptestMatcher(r *http.Request, i cassette.Request) bool {
// Ignore the port in the HTTP request URL
r_url := *r.URL
r_url.Host = r_url.Hostname()
// Ignore the port in the cassette request URL
i_url, err := url.Parse(i.URL)
if err != nil {
return false
}
i_url.Host = i_url.Hostname()
return r.Method == i.Method && r_url.String() == i_url.String()
}
func testHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Request-Id", r.Header.Get("Request-Id"))
w.WriteHeader(http.StatusOK)
}
func TestVcr(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(testHandler))
defer server.Close()
rec, err := recorder.New("fixtures/" + t.Name())
if err != nil {
t.Fatal(err)
}
defer rec.Stop()
rec.SetMatcher(httptestMatcher)
req, err := http.NewRequest(http.MethodGet, server.URL, nil)
if err != nil {
t.Fatal(err)
}
req_id := uuid.NewString()
req.Header.Add("Request-Id", req_id)
resp, err := rec.GetDefaultClient().Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
resp_id := resp.Header.Get("Request-Id")
if resp_id != req_id {
t.Errorf("Expected Request-Id %s, got %s", req_id, resp_id)
}
} |
Hey @quad , I'll try to look into this one soon. Thanks for providing some sample code to test out. |
Hey @quad , I think the issue you have is because during each test execution you are creating a new UUID and then try to compare it against the one which is already in the recorded interaction. This code here would always generate a new UUID, which will be different than the recorded one. req_id := uuid.NewString()
req.Header.Add("Request-Id", req_id) A possible solution would be to use a This is what you need to add to your recorder. // BeforeResponseHook which will return the ID we create above
hook := func(i *cassette.Interaction) error {
i.Response.Headers.Set("Request-Id", req_id)
return nil
}
rec.AddHook(hook, recorder.BeforeResponseReplayHook) The full code is here as well. package main
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/google/uuid"
"gopkg.in/dnaeon/go-vcr.v3/cassette"
"gopkg.in/dnaeon/go-vcr.v3/recorder"
)
func httptestMatcher(r *http.Request, i cassette.Request) bool {
// Ignore the port in the HTTP request URL
r_url := *r.URL
r_url.Host = r_url.Hostname()
// Ignore the port in the cassette request URL
i_url, err := url.Parse(i.URL)
if err != nil {
return false
}
i_url.Host = i_url.Hostname()
return r.Method == i.Method && r_url.String() == i_url.String()
}
func testHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Request-Id", r.Header.Get("Request-Id"))
w.WriteHeader(http.StatusOK)
}
func TestVcr(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(testHandler))
defer server.Close()
rec, err := recorder.New("fixtures/" + t.Name())
if err != nil {
t.Fatal(err)
}
defer rec.Stop()
rec.SetMatcher(httptestMatcher)
req, err := http.NewRequest(http.MethodGet, server.URL, nil)
if err != nil {
t.Fatal(err)
}
req_id := uuid.NewString()
req.Header.Add("Request-Id", req_id)
// BeforeResponseHook which will return the ID we create above
hook := func(i *cassette.Interaction) error {
i.Response.Headers.Set("Request-Id", req_id)
return nil
}
rec.AddHook(hook, recorder.BeforeResponseReplayHook)
resp, err := rec.GetDefaultClient().Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
resp_id := resp.Header.Get("Request-Id")
if resp_id != req_id {
t.Errorf("Expected Request-Id %s, got %s", req_id, resp_id)
}
} |
💯 I used per-request unique UUID as a motivating example; but, yes, the recorded response needs to be mutated as a function of unique per-request data.
That's a neat solution! I hadn't considered capturing I don't immediately see how this pattern could work across a cassette that recorded multiple requests; but, I'll play with it more. Thank you! 🙇 |
One thing that comes to mind is to have a hook, where you keep mapping between API paths (e.g. Example hook (haven't tested it, but it should be good enough to illustrate the idea). hook := func(i *cassette.Interaction) error {
req, err := i.GetHTTPRequest()
if err != nil {
return err
}
// Mapping between relative URLs and the expected UUIDs
mappings := map[string]string{
"/api/v1/foo": "uuid-1",
"/api/v1/bar": "uuid-2",
}
for path, id := range mappings {
if req.URL.Path == path {
i.Response.Headers.Set("Request-Id", id)
}
}
return nil
} |
The problem is that the UUIDs are randomly generated; the request ID can be thought of as an idempotency key. In a real example, the UUID would be generated per-request by the client library under test. At this point, what I'd love is a way to access the actual request in |
We have request to a server that include a unique ID that needs to be included in the response:
Request:
Response:
The request is generated with code like the following:
Right now, the first interaction will be recorded with the first random ID. All future replays will include the original
id
.AfterCaptureHook
norBeforeSaveHook
trigger on replays, so they can't patch up the response with the correctid
BeforeResponseReplayHook
does trigger on replays, but thei.Request
contains the first recordedid
value, so it can't patch up the response with the correctid
What is the correct way to go about fixing up an interaction in this way?
The text was updated successfully, but these errors were encountered: