Documentation
¶
Index ¶
- Variables
- 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(routeID string) error
- 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 WithJSONLogs() ServerOption
- func WithLogFile(logFile string) ServerOption
- func WithLogLevel(level zerolog.Level) ServerOption
- func WithLogger(l zerolog.Logger) ServerOption
- func WithPort(port int) ServerOption
- func WithRoutes(routes []*Route) ServerOption
- func WithSaveFile(saveFile string) ServerOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrNilRoute = errors.New("route is nil") ErrNoMethod = errors.New("no method specified") ErrInvalidPath = errors.New("invalid path") 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") 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") )
Functions ¶
This section is empty.
Types ¶
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
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.Wake(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
if err != nil {
panic(err)
}
}()
client := resty.New()
client.SetBaseURL(fmt.Sprintf("http://localhost:%d", port)) // The URL of the parrot server
waitForParrotServer(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
resp, err := client.R().SetBody(route).Post("/routes")
if err != nil {
panic(err)
}
// Use the host 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
host := "localhost"
// Create a new recorder with our host
recorder, err := parrot.NewRecorder(parrot.WithHost(host))
if err != nil {
panic(err)
}
// Register the recorder with the parrot instance
resp, err = client.R().SetBody(recorder).Post("/record")
if err != nil {
panic(err)
}
if resp.StatusCode() != http.StatusCreated {
panic(fmt.Sprintf("failed to register recorder, got %d status code", resp.StatusCode()))
}
go func() { // Some other service calls the /test route
_, err := client.R().Get("/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)
}
}
Output: GET:/test GET 200 Squawk
Example (Internal) ¶
package main
import (
"context"
"fmt"
"net/http"
"os"
"github.com/rs/zerolog"
"github.com/smartcontractkit/chainlink-testing-framework/parrot"
)
func main() {
saveFile := "recorder_example.json"
p, err := parrot.Wake(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)
}
// 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)
}
}
}
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
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"`
// Handler is the dynamic handler function to use when called
// Can only be set upon creation of the server
Handler http.HandlerFunc `json:"-"`
// 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 {
// RouteCallID is a unique identifier for the route call for help with debugging
RouteCallID string `json:"route_call_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
func Wake ¶
func Wake(options ...ServerOption) (*Server, error)
Wake 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) Register ¶
Register adds a new route to the parrot
Example (External) ¶
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.Wake(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
if err != nil {
panic(err)
}
}()
// Code that calls the parrot server from another service
// Use resty to make HTTP calls to the parrot server
client := resty.New()
client.SetBaseURL(fmt.Sprintf("http://localhost:%d", port)) // The URL of the parrot server
waitForParrotServer(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,
}
resp, err := client.R().SetBody(route).Post("/routes")
if err != nil {
panic(err)
}
defer resp.RawResponse.Body.Close()
fmt.Println(resp.StatusCode())
// Get all routes from the parrot server
routes := make([]*parrot.Route, 0)
resp, err = client.R().SetResult(&routes).Get("/routes")
if err != nil {
panic(err)
}
defer resp.RawResponse.Body.Close()
fmt.Println(resp.StatusCode())
fmt.Println(len(routes))
// Delete the route
resp, err = client.R().SetBody(route).Delete("/routes")
if err != nil {
panic(err)
}
defer resp.RawResponse.Body.Close()
fmt.Println(resp.StatusCode())
// Get all routes from the parrot server
routes = make([]*parrot.Route, 0)
resp, err = client.R().SetResult(&routes).Get("/routes")
if err != nil {
panic(err)
}
defer resp.RawResponse.Body.Close()
fmt.Println(len(routes))
Output: 201 200 1 204 0
Example (Internal) ¶
package main
import (
"context"
"fmt"
"net/http"
"os"
"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.Wake(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,
}
// 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
err = p.Delete(route.ID())
if err != nil {
panic(err)
}
// Get all routes from the parrot instance
routes = p.Routes()
fmt.Println(len(routes))
}
Output: 200 Squawk 1 0
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 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 WithLogger ¶
func WithLogger(l zerolog.Logger) ServerOption
WithLogger sets the logger for the ParrotServer
func WithPort ¶
func WithPort(port int) ServerOption
WithPort sets the port for the ParrotServer to run on
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