Documentation
¶
Index ¶
- Constants
- Variables
- type Client
- func (c *Client) CallRoute(method, path string) (*resty.Response, error)
- func (c *Client) DeleteRoute(route *Route) error
- func (c *Client) Healthy() (bool, error)
- func (c *Client) Recorders() ([]string, error)
- func (c *Client) RegisterRecorder(recorder *Recorder) error
- func (c *Client) RegisterRoute(route *Route) error
- func (c *Client) Routes() ([]*Route, error)
- type Recorder
- type RecorderOption
- type Route
- type RouteCall
- type RouteCallRequest
- type RouteCallResponse
- type SaveFile
- type Server
- func (p *Server) Address() string
- func (p *Server) Call(method, path string) (*resty.Response, error)
- func (p *Server) Delete(route *Route)
- func (p *Server) Healthy() error
- func (p *Server) Host() string
- func (p *Server) Port() int
- func (p *Server) Record(recorderURL string) error
- func (p *Server) Recorders() []string
- func (p *Server) Register(route *Route) error
- func (p *Server) Routes() []*Route
- func (p *Server) Shutdown(ctx context.Context) error
- func (p *Server) WaitShutdown()
- type ServerOption
- func DisableConsoleLogs() ServerOption
- func WithHost(host string) ServerOption
- func WithJSONLogs() ServerOption
- func WithLogFile(logFile string) ServerOption
- func WithLogLevel(level zerolog.Level) ServerOption
- func WithPort(port int) ServerOption
- func WithRecorders(recorders ...string) ServerOption
- func WithRoutes(routes []*Route) ServerOption
- func WithSaveFile(saveFile string) ServerOption
Examples ¶
Constants ¶
const ( HealthRoute = "/health" RoutesRoute = "/routes" RecorderRoute = "/recorder" // MethodAny is a wildcard for any HTTP method MethodAny = "ANY" )
Variables ¶
var ( ErrNilRoute = errors.New("route is nil") ErrInvalidPath = errors.New("invalid path") ErrInvalidMethod = errors.New("invalid method") ErrNoResponse = errors.New("route must have a handler or some response") ErrOnlyOneResponse = errors.New("route can only have one response type") ErrResponseMarshal = errors.New("unable to marshal response body to JSON") ErrRouteNotFound = errors.New("route not found") ErrWildcardPath = fmt.Errorf("path can only contain one wildcard '*' and it must be the final value") ErrNoRecorderURL = errors.New("no recorder URL specified") ErrInvalidRecorderURL = errors.New("invalid recorder URL") ErrRecorderNotFound = errors.New("recorder not found") ErrServerShutdown = errors.New("parrot is already asleep") ErrServerUnhealthy = errors.New("parrot is unhealthy") )
Functions ¶
This section is empty.
Types ¶
type Client ¶ added in v0.4.0
type Client struct {
// contains filtered or unexported fields
}
Client interacts with a parrot server
func NewClient ¶ added in v0.4.0
NewClient creates a new client for a parrot server running at the given url.
func (*Client) DeleteRoute ¶ added in v0.4.0
DeleteRoute deletes a route on the server
func (*Client) Recorders ¶ added in v0.4.0
Recorders returns all the recorders registered on the server
func (*Client) RegisterRecorder ¶ added in v0.4.0
RegisterRecorder registers a recorder on the server
func (*Client) RegisterRoute ¶ added in v0.4.0
RegisterRoute registers a route on the server
type Recorder ¶
type Recorder struct { Host string `json:"host"` Port string `json:"port"` // contains filtered or unexported fields }
Recorder records route calls
Example (External) ¶
Example of how to use parrot recording when calling it from an external service
package main import ( "fmt" "net/http" "os" "time" "github.com/rs/zerolog" "github.com/smartcontractkit/chainlink-testing-framework/parrot" ) func main() { var ( saveFile = "recorder_example.json" port = 9091 ) defer os.Remove(saveFile) // Cleanup the save file for the example go func() { // Run the parrot server as a separate instance, like in a Docker container _, err := parrot.NewServer(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile)) if err != nil { panic(err) } }() client := parrot.NewClient(fmt.Sprintf("http://localhost:%d", port)) waitForParrotServerExternal(client, time.Second) // Wait for the parrot server to start // Register a new route /test that will return a 200 status code with a text/plain response body of "Squawk" route := &parrot.Route{ Method: http.MethodGet, Path: "/test", RawResponseBody: "Squawk", ResponseStatusCode: http.StatusOK, } // Register the route with the parrot instance err := client.RegisterRoute(route) if err != nil { panic(err) } // Use the recorderHost of the machine your recorder is running on // This should not be localhost if you are running the parrot server on a different machine // It should be the public IP address of the machine running your code, so that the parrot can call back to it recorderHost := "localhost" // Create a new recorder with our host recorder, err := parrot.NewRecorder(parrot.WithRecorderHost(recorderHost)) if err != nil { panic(err) } // Register the recorder with the parrot instance err = client.RegisterRecorder(recorder) if err != nil { panic(err) } recorders, err := client.Recorders() if err != nil { panic(err) } fmt.Printf("Found %d recorders\n", len(recorders)) go func() { // Some other service calls the /test route _, err := client.CallRoute(http.MethodGet, "/test") if err != nil { panic(err) } }() // You can now listen to the recorder for all route calls for { select { case recordedRouteCall := <-recorder.Record(): if recordedRouteCall.RouteID == route.ID() { fmt.Println(recordedRouteCall.RouteID) fmt.Println(recordedRouteCall.Request.Method) fmt.Println(recordedRouteCall.Response.StatusCode) fmt.Println(string(recordedRouteCall.Response.Body)) return } case err := <-recorder.Err(): panic(err) } } } // waitForParrotServerExternal checks the parrot server health endpoint until it returns a 200 status code or the timeout is reached func waitForParrotServerExternal(client *parrot.Client, timeoutDur time.Duration) { ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() timeout := time.NewTimer(timeoutDur) for { select { case <-ticker.C: healthy, err := client.Healthy() if err != nil { continue } if healthy { return } case <-timeout.C: panic("timeout waiting for parrot server to start") } } }
Output: Found 1 recorders GET:/test GET 200 Squawk
Example (Internal) ¶
package main import ( "context" "fmt" "net/http" "os" "time" "github.com/rs/zerolog" "github.com/smartcontractkit/chainlink-testing-framework/parrot" ) func main() { saveFile := "recorder_example.json" p, err := parrot.NewServer(parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile)) if err != nil { panic(err) } defer func() { // Cleanup the parrot instance err = p.Shutdown(context.Background()) // Gracefully shutdown the parrot instance if err != nil { panic(err) } p.WaitShutdown() // Wait for the parrot instance to shutdown. Usually unnecessary, but we want to clean up the save file os.Remove(saveFile) // Cleanup the save file for the example }() // Create a new recorder recorder, err := parrot.NewRecorder() if err != nil { panic(err) } waitForParrotServerInternal(p, time.Second) // Wait for the parrot server to start // Register the recorder with the parrot instance err = p.Record(recorder.URL()) if err != nil { panic(err) } defer recorder.Close() // Register a new route /test that will return a 200 status code with a text/plain response body of "Squawk" route := &parrot.Route{ Method: http.MethodGet, Path: "/test", RawResponseBody: "Squawk", ResponseStatusCode: http.StatusOK, } err = p.Register(route) if err != nil { panic(err) } // Call the route go func() { _, err := p.Call(http.MethodGet, "/test") if err != nil { panic(err) } }() // Record the route call for { select { case recordedRouteCall := <-recorder.Record(): if recordedRouteCall.RouteID == route.ID() { fmt.Println(recordedRouteCall.RouteID) fmt.Println(recordedRouteCall.Request.Method) fmt.Println(recordedRouteCall.Response.StatusCode) fmt.Println(string(recordedRouteCall.Response.Body)) return } case err := <-recorder.Err(): panic(err) } } } func waitForParrotServerInternal(p *parrot.Server, timeoutDur time.Duration) { ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() timeout := time.NewTimer(timeoutDur) for { select { case <-ticker.C: if err := p.Healthy(); err == nil { return } case <-timeout.C: panic("timeout waiting for parrot server to start") } } }
Output: GET:/test GET 200 Squawk
func NewRecorder ¶
func NewRecorder(opts ...RecorderOption) (*Recorder, error)
NewRecorder creates a new recorder that listens for incoming requests to the parrot server
func (*Recorder) URL ¶
URL returns the URL of the recorder to send requests to WARNING: This URL automatically binds to the first available port on the host machine and the host will be 0.0.0.0 or localhost. If you're calling this from a different machine you will need to replace the host with the IP address of the machine running the recorder.
type RecorderOption ¶
type RecorderOption func(*Recorder)
RecorderOption is a function that modifies a recorder
func WithRecorderHost ¶ added in v0.6.1
func WithRecorderHost(host string) RecorderOption
WithRecorderHost sets the host of the recorder
type Route ¶
type Route struct { // Method is the HTTP method to match Method string `json:"Method"` // Path is the URL path to match Path string `json:"Path"` // RawResponseBody is the static, raw string response to return when called RawResponseBody string `json:"raw_response_body"` // ResponseBody will be marshalled to JSON and returned when called ResponseBody any `json:"response_body"` // ResponseStatusCode is the HTTP status code to return when called ResponseStatusCode int `json:"response_status_code"` }
Route holds information about the mock route configuration
type RouteCall ¶
type RouteCall struct { // ID is a unique identifier for the route call for help with debugging ID string `json:"id"` // RouteID is the identifier of the route that was called RouteID string `json:"route_id"` // Request is the request made to the route Request *RouteCallRequest `json:"request"` // Response is the response from the route Response *RouteCallResponse `json:"response"` }
RouteCall records when a route is called, the request and response
type RouteCallRequest ¶
type RouteCallRequest struct { Method string `json:"method"` URL *url.URL `json:"url"` RemoteAddr string `json:"caller"` Header http.Header `json:"header"` Body []byte `json:"body"` }
RouteCallRequest records the request made to a route
type RouteCallResponse ¶
type RouteCallResponse struct { StatusCode int `json:"status_code"` Header http.Header `json:"header"` Body []byte `json:"body"` }
RouteCallResponse records the response from a route
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a mock HTTP server that can register and respond to dynamic routes
Example (External) ¶
package main import ( "fmt" "net/http" "os" "time" "github.com/rs/zerolog" "github.com/smartcontractkit/chainlink-testing-framework/parrot" ) func main() { var ( saveFile = "route_example.json" port = 9090 ) defer os.Remove(saveFile) // Cleanup the save file for the example go func() { // Run the parrot server as a separate instance, like in a Docker container _, err := parrot.NewServer(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile)) if err != nil { panic(err) } }() // Get a client to interact with the parrot server client := parrot.NewClient(fmt.Sprintf("http://localhost:%d", port)) waitForParrotServerExternal(client, time.Second) // Wait for the parrot server to start // Register a new route /test that will return a 200 status code with a text/plain response body of "Squawk" route := &parrot.Route{ Method: http.MethodGet, Path: "/test", RawResponseBody: "Squawk", ResponseStatusCode: http.StatusOK, } err := client.RegisterRoute(route) if err != nil { panic(err) } fmt.Println("Registered route") // Get all routes from the parrot server routes, err := client.Routes() if err != nil { panic(err) } fmt.Printf("Found %d routes\n", len(routes)) // Delete the route err = client.DeleteRoute(route) if err != nil { panic(err) } fmt.Println("Deleted route") // Get all routes from the parrot server routes, err = client.Routes() if err != nil { panic(err) } fmt.Printf("Found %d routes\n", len(routes)) } // waitForParrotServerExternal checks the parrot server health endpoint until it returns a 200 status code or the timeout is reached func waitForParrotServerExternal(client *parrot.Client, timeoutDur time.Duration) { ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() timeout := time.NewTimer(timeoutDur) for { select { case <-ticker.C: healthy, err := client.Healthy() if err != nil { continue } if healthy { return } case <-timeout.C: panic("timeout waiting for parrot server to start") } } }
Output: Registered route Found 1 routes Deleted route Found 0 routes
Example (Internal) ¶
package main import ( "context" "fmt" "net/http" "os" "time" "github.com/rs/zerolog" "github.com/smartcontractkit/chainlink-testing-framework/parrot" ) func main() { // Create a new parrot instance with no logging and a custom save file saveFile := "register_example.json" p, err := parrot.NewServer(parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile)) if err != nil { panic(err) } defer func() { // Cleanup the parrot instance err = p.Shutdown(context.Background()) // Gracefully shutdown the parrot instance if err != nil { panic(err) } p.WaitShutdown() // Wait for the parrot instance to shutdown. Usually unnecessary, but we want to clean up the save file os.Remove(saveFile) // Cleanup the save file for the example }() // Create a new route /test that will return a 200 status code with a text/plain response body of "Squawk" route := &parrot.Route{ Method: http.MethodGet, Path: "/test", RawResponseBody: "Squawk", ResponseStatusCode: http.StatusOK, } waitForParrotServerInternal(p, time.Second) // Wait for the parrot server to start // Register the route with the parrot instance err = p.Register(route) if err != nil { panic(err) } // Call the route resp, err := p.Call(http.MethodGet, "/test") if err != nil { panic(err) } fmt.Println(resp.StatusCode()) fmt.Println(string(resp.Body())) // Get all routes from the parrot instance routes := p.Routes() fmt.Println(len(routes)) // Delete the route p.Delete(route) // Get all routes from the parrot instance routes = p.Routes() fmt.Println(len(routes)) } func waitForParrotServerInternal(p *parrot.Server, timeoutDur time.Duration) { ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() timeout := time.NewTimer(timeoutDur) for { select { case <-ticker.C: if err := p.Healthy(); err == nil { return } case <-timeout.C: panic("timeout waiting for parrot server to start") } } }
Output: 200 Squawk 1 0
func NewServer ¶ added in v0.4.0
func NewServer(options ...ServerOption) (*Server, error)
NewServer creates a new Parrot server with dynamic route handling
func (*Server) Record ¶
Record registers a new recorder with the parrot. All incoming requests to the parrot will be sent to the recorder.
func (*Server) WaitShutdown ¶
func (p *Server) WaitShutdown()
WaitShutdown blocks until the parrot server has shut down
type ServerOption ¶
ServerOption defines functional options for configuring the ParrotServer
func DisableConsoleLogs ¶ added in v0.1.7
func DisableConsoleLogs() ServerOption
DisableConsoleLogs disables logging to the console
func WithHost ¶
func WithHost(host string) ServerOption
WithHost sets the address for the ParrotServer to run on
func WithJSONLogs ¶
func WithJSONLogs() ServerOption
WithJSONLogs sets the logger to output JSON logs
func WithLogFile ¶
func WithLogFile(logFile string) ServerOption
WithLogFile sets the file to save the logs to
func WithLogLevel ¶
func WithLogLevel(level zerolog.Level) ServerOption
WithLogLevel sets the visible log level of the default logger
func WithPort ¶
func WithPort(port int) ServerOption
WithPort sets the port for the ParrotServer to run on
func WithRecorders ¶ added in v0.2.0
func WithRecorders(recorders ...string) ServerOption
WithRecorders sets the initial recorders for the Parrot
func WithRoutes ¶
func WithRoutes(routes []*Route) ServerOption
WithRoutes sets the initial routes for the Parrot
func WithSaveFile ¶
func WithSaveFile(saveFile string) ServerOption
WithSaveFile sets the file to save the routes to