Skip to content

Commit c25306f

Browse files
committed
Examples
1 parent 93ad4a1 commit c25306f

File tree

4 files changed

+306
-125
lines changed

4 files changed

+306
-125
lines changed

parrot/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ var (
1717
ErrNoRecorderURL = errors.New("no recorder URL specified")
1818
ErrInvalidRecorderURL = errors.New("invalid recorder URL")
1919
ErrRecorderNotFound = errors.New("recorder not found")
20+
21+
ErrParrotAsleep = errors.New("parrot is asleep")
2022
)
2123

2224
// Custom error type to help add more detail to base errors

parrot/examples_test.go

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
package parrot_test
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"net/http"
78

9+
"github.com/go-resty/resty/v2"
810
"github.com/rs/zerolog"
911
"github.com/smartcontractkit/chainlink-testing-framework/parrot"
1012
)
1113

12-
func ExampleServer() {
14+
func ExampleRegister() {
1315
// Create a new parrot instance with no logging
1416
p, err := parrot.Wake(parrot.WithLogLevel(zerolog.NoLevel))
1517
if err != nil {
1618
panic(err)
1719
}
20+
defer func() {
21+
err = p.Shutdown(context.Background())
22+
if err != nil {
23+
panic(err)
24+
}
25+
}()
1826

1927
// Create a new route /test that will return a 200 status code with a text/plain response body of "Squawk"
2028
route := &parrot.Route{
@@ -44,3 +52,140 @@ func ExampleServer() {
4452
// 200
4553
// Squawk
4654
}
55+
56+
func ExampleRoute() {
57+
// Run the parrot server as a separate instance, like in a Docker container
58+
p, err := parrot.Wake(parrot.WithPort(9090), parrot.WithLogLevel(zerolog.NoLevel))
59+
if err != nil {
60+
panic(err)
61+
}
62+
defer func() {
63+
err = p.Shutdown(context.Background())
64+
if err != nil {
65+
panic(err)
66+
}
67+
}()
68+
69+
// Code that calls the parrot server from another service
70+
// Use resty to make HTTP calls to the parrot server
71+
client := resty.New()
72+
73+
// Register a new route /test that will return a 200 status code with a text/plain response body of "Squawk"
74+
route := &parrot.Route{
75+
Method: http.MethodGet,
76+
Path: "/test",
77+
RawResponseBody: "Squawk",
78+
ResponseStatusCode: 200,
79+
}
80+
resp, err := client.R().SetBody(route).Post("http://localhost:9090/routes")
81+
if err != nil {
82+
panic(err)
83+
}
84+
defer resp.RawResponse.Body.Close()
85+
fmt.Println(resp.StatusCode())
86+
87+
// Get all routes from the parrot server
88+
routes := make([]*parrot.Route, 0)
89+
resp, err = client.R().SetResult(&routes).Get("http://localhost:9090/routes")
90+
if err != nil {
91+
panic(err)
92+
}
93+
defer resp.RawResponse.Body.Close()
94+
fmt.Println(resp.StatusCode())
95+
fmt.Println(len(routes))
96+
97+
// Delete the route
98+
req := &parrot.RouteRequest{
99+
ID: route.ID(),
100+
}
101+
resp, err = client.R().SetBody(req).Delete("http://localhost:9090/routes")
102+
if err != nil {
103+
panic(err)
104+
}
105+
defer resp.RawResponse.Body.Close()
106+
fmt.Println(resp.StatusCode())
107+
108+
// Get all routes from the parrot server
109+
routes = make([]*parrot.Route, 0)
110+
resp, err = client.R().SetResult(&routes).Get("http://localhost:9090/routes")
111+
if err != nil {
112+
panic(err)
113+
}
114+
defer resp.RawResponse.Body.Close()
115+
fmt.Println(len(routes))
116+
117+
// Output:
118+
// 201
119+
// 200
120+
// 1
121+
// 204
122+
// 0
123+
}
124+
125+
func ExampleRecorder() {
126+
p, err := parrot.Wake(parrot.WithLogLevel(zerolog.NoLevel))
127+
if err != nil {
128+
panic(err)
129+
}
130+
defer func() {
131+
err = p.Shutdown(context.Background())
132+
if err != nil {
133+
panic(err)
134+
}
135+
}()
136+
137+
// Create a new recorder
138+
recorder, err := parrot.NewRecorder()
139+
if err != nil {
140+
panic(err)
141+
}
142+
143+
// Register the recorder with the parrot instance
144+
err = p.Record(recorder.URL)
145+
if err != nil {
146+
panic(err)
147+
}
148+
defer recorder.Close()
149+
150+
// Register a new route /test that will return a 200 status code with a text/plain response body of "Squawk"
151+
route := &parrot.Route{
152+
Method: http.MethodGet,
153+
Path: "/test",
154+
RawResponseBody: "Squawk",
155+
ResponseStatusCode: http.StatusOK,
156+
}
157+
err = p.Register(route)
158+
if err != nil {
159+
panic(err)
160+
}
161+
162+
// Call the route
163+
go func() {
164+
resp, err := p.Call(http.MethodGet, "/test")
165+
if err != nil {
166+
panic(err)
167+
}
168+
defer resp.Body.Close()
169+
}()
170+
171+
// Record the route call
172+
for {
173+
select {
174+
case recordedRouteCall := <-recorder.Record():
175+
if recordedRouteCall.RouteID == route.ID() {
176+
fmt.Println(recordedRouteCall.RouteID)
177+
fmt.Println(recordedRouteCall.Request.Method)
178+
fmt.Println(recordedRouteCall.Response.StatusCode)
179+
fmt.Println(string(recordedRouteCall.Response.Body))
180+
return
181+
}
182+
case err := <-recorder.Err():
183+
panic(err)
184+
}
185+
}
186+
// Output:
187+
// GET:/test
188+
// GET
189+
// 200
190+
// Squawk
191+
}

parrot/parrot.go

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type Server struct {
5656
host string
5757
address string
5858

59+
shutDown bool
5960
saveFileName string
6061
useCustomLogger bool
6162
logFileName string
@@ -203,8 +204,8 @@ func Wake(options ...ServerOption) (*Server, error) {
203204
}
204205

205206
mux := http.NewServeMux()
206-
mux.HandleFunc("/register", p.registerRouteHandler)
207-
mux.HandleFunc("/record", p.recordHandler)
207+
// TODO: Add a route to
208+
mux.HandleFunc("/routes", p.routesHandler)
208209
mux.HandleFunc("/", p.dynamicHandler)
209210

210211
p.server = &http.Server{
@@ -241,6 +242,7 @@ func (p *Server) run(listener net.Listener) {
241242

242243
// Shutdown gracefully shuts down the parrot server
243244
func (p *Server) Shutdown(ctx context.Context) error {
245+
p.shutDown = true
244246
p.log.Info().Msg("Putting cloth over the parrot's cage...")
245247
return p.server.Shutdown(ctx)
246248
}
@@ -301,64 +303,86 @@ func (p *Server) Register(route *Route) error {
301303
return nil
302304
}
303305

304-
// registerRouteHandler handles the dynamic route registration.
305-
func (p *Server) registerRouteHandler(w http.ResponseWriter, r *http.Request) {
306-
registerLogger := zerolog.Ctx(r.Context())
306+
// routesHandler handles the dynamic route registration.
307+
func (p *Server) routesHandler(w http.ResponseWriter, r *http.Request) {
308+
routesLogger := zerolog.Ctx(r.Context())
307309
if r.Method == http.MethodDelete {
308310
var routeRequest *RouteRequest
309311
if err := json.NewDecoder(r.Body).Decode(&routeRequest); err != nil {
310312
http.Error(w, "Invalid request body", http.StatusBadRequest)
311-
registerLogger.Debug().Err(err).Msg("Failed to decode request body")
313+
routesLogger.Debug().Err(err).Msg("Failed to decode request body")
312314
return
313315
}
314316
defer r.Body.Close()
315317

316318
if routeRequest.ID == "" {
317319
http.Error(w, "Route ID required", http.StatusBadRequest)
318-
registerLogger.Debug().Msg("No Route ID provided")
320+
routesLogger.Debug().Msg("No Route ID provided")
319321
return
320322
}
321323

322324
err := p.Unregister(routeRequest.ID)
323325
if err != nil {
324326
http.Error(w, err.Error(), http.StatusBadRequest)
325-
registerLogger.Debug().Err(err).Msg("Failed to unregister route")
327+
routesLogger.Debug().Err(err).Msg("Failed to unregister route")
326328
return
327329
}
328330

329331
w.WriteHeader(http.StatusNoContent)
330-
registerLogger.Info().
332+
routesLogger.Info().
331333
Str("Route ID", routeRequest.ID).
332334
Msg("Route unregistered")
333-
} else if r.Method == http.MethodPost {
335+
return
336+
}
337+
338+
if r.Method == http.MethodPost {
334339
var route *Route
335340
if err := json.NewDecoder(r.Body).Decode(&route); err != nil {
336341
http.Error(w, "Invalid request body", http.StatusBadRequest)
337-
registerLogger.Debug().Err(err).Msg("Failed to decode request body")
342+
routesLogger.Debug().Err(err).Msg("Failed to decode request body")
338343
return
339344
}
340345
defer r.Body.Close()
341346

342347
if route.Method == "" || route.Path == "" {
343348
err := errors.New("Method and path are required")
344349
http.Error(w, err.Error(), http.StatusBadRequest)
345-
registerLogger.Debug().Err(err).Msg("Method and path are required")
350+
routesLogger.Debug().Err(err).Msg("Method and path are required")
346351
return
347352
}
348353

349354
err := p.Register(route)
350355
if err != nil {
351356
http.Error(w, err.Error(), http.StatusBadRequest)
352-
registerLogger.Debug().Err(err).Msg("Failed to register route")
357+
routesLogger.Debug().Err(err).Msg("Failed to register route")
353358
return
354359
}
355360

356361
w.WriteHeader(http.StatusCreated)
357-
} else {
358-
http.Error(w, "Invalid method, only use POST or DELETE", http.StatusMethodNotAllowed)
359-
registerLogger.Debug().Msg("Invalid method")
360362
return
361363
}
364+
365+
if r.Method == http.MethodGet {
366+
jsonRoutes, err := json.Marshal(p.Routes())
367+
if err != nil {
368+
http.Error(w, "Failed to marshal routes", http.StatusInternalServerError)
369+
routesLogger.Debug().Err(err).Msg("Failed to marshal routes")
370+
return
371+
}
372+
373+
w.Header().Set("Content-Type", "application/json")
374+
if _, err = w.Write(jsonRoutes); err != nil {
375+
http.Error(w, "Failed to write response", http.StatusInternalServerError)
376+
routesLogger.Debug().Err(err).Msg("Failed to write response")
377+
return
378+
}
379+
380+
routesLogger.Debug().Msg("Returned routes")
381+
return
382+
}
383+
384+
http.Error(w, "Invalid method", http.StatusMethodNotAllowed)
385+
routesLogger.Debug().Msg("Invalid method")
362386
}
363387

364388
// Record registers a new recorder with the parrot. All incoming requests to the parrot will be sent to the recorder.
@@ -376,32 +400,6 @@ func (p *Server) Record(recorderURL string) error {
376400
return nil
377401
}
378402

379-
func (p *Server) recordHandler(w http.ResponseWriter, r *http.Request) {
380-
recordLogger := zerolog.Ctx(r.Context())
381-
if r.Method != http.MethodPost {
382-
http.Error(w, "Invalid method, only use POST or DELETE", http.StatusMethodNotAllowed)
383-
recordLogger.Debug().Msg("Invalid method")
384-
return
385-
}
386-
387-
var recorder *Recorder
388-
if err := json.NewDecoder(r.Body).Decode(&recorder); err != nil {
389-
http.Error(w, "Invalid request body", http.StatusBadRequest)
390-
recordLogger.Err(err).Msg("Failed to decode request body")
391-
return
392-
}
393-
394-
err := p.Record(recorder.URL)
395-
if err != nil {
396-
http.Error(w, err.Error(), http.StatusBadRequest)
397-
recordLogger.Debug().Err(err).Msg("Failed to add recorder")
398-
return
399-
}
400-
401-
w.WriteHeader(http.StatusCreated)
402-
recordLogger.Info().Str("Recorder URL", recorder.URL).Msg("Recorder added")
403-
}
404-
405403
// Unregister removes a route from the parrot
406404
func (p *Server) Unregister(routeID string) error {
407405
p.routesMu.RLock()
@@ -428,6 +426,17 @@ func (p *Server) Call(method, path string) (*http.Response, error) {
428426
return client.Do(req)
429427
}
430428

429+
func (p *Server) Routes() []*Route {
430+
p.routesMu.RLock()
431+
defer p.routesMu.RUnlock()
432+
433+
routes := make([]*Route, 0, len(p.routes))
434+
for _, route := range p.routes {
435+
routes = append(routes, route)
436+
}
437+
return routes
438+
}
439+
431440
// dynamicHandler handles all incoming requests and responds based on the registered routes.
432441
func (p *Server) dynamicHandler(w http.ResponseWriter, r *http.Request) {
433442
p.routesMu.RLock()

0 commit comments

Comments
 (0)