Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 67 additions & 5 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package gock

import (
"bytes"
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
)

// MapResponseFunc represents the required function interface impletemed by response mappers.
// MapResponseFunc represents the required function interface implemented by response mappers.
type MapResponseFunc func(*http.Response) *http.Response

// FilterResponseFunc represents the required function interface impletemed by response filters.
// FilterResponseFunc represents the required function interface implemented by response filters.
type FilterResponseFunc func(*http.Response) bool

// Response represents high-level HTTP fields to configure
Expand Down Expand Up @@ -82,7 +84,7 @@ func (r *Response) SetHeader(key, value string) *Response {
}

// AddHeader adds a new header field in the mock response
// with out removing an existent one.
// without removing an existent one.
func (r *Response) AddHeader(key, value string) *Response {
r.Header.Add(key, value)
return r
Expand Down Expand Up @@ -143,7 +145,6 @@ func (r *Response) SetError(err error) *Response {
}

// Delay defines the response simulated delay.
// This feature is still experimental and will be improved in the future.
func (r *Response) Delay(delay time.Duration) *Response {
r.ResponseDelay = delay
return r
Expand All @@ -161,7 +162,7 @@ func (r *Response) Filter(fn FilterResponseFunc) *Response {
return r
}

// EnableNetworking enables the use real networking for the current mock.
// EnableNetworking enables the use of real networking for the current mock.
func (r *Response) EnableNetworking() *Response {
r.UseNetwork = true
return r
Expand All @@ -172,6 +173,67 @@ func (r *Response) Done() bool {
return r.Mock.Done()
}

// Respond generates the HTTP response for the mock, respecting context deadlines
func (r *Response) Respond(req *http.Request) (*http.Response, error) {
if r.Error != nil {
return nil, r.Error
}

// Handle response delay while respecting context deadline
if r.ResponseDelay > 0 {
if deadline, ok := req.Context().Deadline(); ok {
timeUntilDeadline := time.Until(deadline)
if r.ResponseDelay > timeUntilDeadline {
// Simulate timeout if delay exceeds client timeout
return nil, context.DeadlineExceeded
}
}

// Apply delay, respecting context cancellation
select {
case <-req.Context().Done():
return nil, req.Context().Err()
case <-time.After(r.ResponseDelay):
// Proceed with response
}
}

resp := &http.Response{
StatusCode: r.StatusCode,
Header: r.Header,
Body: ioutil.NopCloser(bytes.NewReader(r.BodyBuffer)),
Request: req,
}
if r.BodyGen != nil {
resp.Body = r.BodyGen()
}
if resp.Header == nil {
resp.Header = make(http.Header)
}
if resp.StatusCode == 0 {
resp.StatusCode = http.StatusOK
}

// Apply cookies
for _, cookie := range r.Cookies {
resp.Header.Add("Set-Cookie", cookie.String())
}

// Apply mappers
for _, mapper := range r.Mappers {
resp = mapper(resp)
}

// Apply filters
for _, filter := range r.Filters {
if !filter(resp) {
return nil, fmt.Errorf("response filtered out")
}
}

return resp, nil
}

func readAndDecode(data interface{}, kind string) ([]byte, error) {
buf := &bytes.Buffer{}

Expand Down