Skip to content

Commit a893931

Browse files
committed
Adds Parrotserver client
1 parent 298d5b3 commit a893931

File tree

6 files changed

+164
-66
lines changed

6 files changed

+164
-66
lines changed

parrot/client.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package parrot
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/go-resty/resty/v2"
8+
)
9+
10+
// Client interacts with a parrot server
11+
type Client struct {
12+
restyClient *resty.Client
13+
}
14+
15+
// NewClient creates a new client for a parrot server running at the given url.
16+
func NewClient(url string) *Client {
17+
restyC := resty.New()
18+
restyC.SetBaseURL(url)
19+
return &Client{
20+
restyClient: restyC,
21+
}
22+
}
23+
24+
// Health returns the health of the server
25+
func (c *Client) Healthy() (bool, error) {
26+
resp, err := c.restyClient.R().Get(HealthRoute)
27+
if err != nil {
28+
return false, err
29+
}
30+
return resp.StatusCode() == http.StatusOK, nil
31+
}
32+
33+
// Routes returns all the routes registered on the server
34+
func (c *Client) Routes() ([]*Route, error) {
35+
routes := []*Route{}
36+
resp, err := c.restyClient.R().SetResult(&routes).Get(RoutesRoute)
37+
if err != nil {
38+
return nil, err
39+
}
40+
if resp.StatusCode() != http.StatusOK {
41+
return nil, fmt.Errorf("failed to get routes, got %d status code: %s", resp.StatusCode(), string(resp.Body()))
42+
}
43+
return routes, nil
44+
}
45+
46+
// CallRoute calls a route on the server
47+
func (c *Client) CallRoute(method, path string) (*resty.Response, error) {
48+
return c.restyClient.R().Execute(method, path)
49+
}
50+
51+
// RegisterRoute registers a route on the server
52+
func (c *Client) RegisterRoute(route *Route) error {
53+
resp, err := c.restyClient.R().SetBody(route).Post(RoutesRoute)
54+
if err != nil {
55+
return err
56+
}
57+
if resp.StatusCode() != http.StatusCreated {
58+
return fmt.Errorf("failed to register route, got %d status code: %s", resp.StatusCode(), string(resp.Body()))
59+
}
60+
return nil
61+
}
62+
63+
// DeleteRoute deletes a route on the server
64+
func (c *Client) DeleteRoute(route *Route) error {
65+
resp, err := c.restyClient.R().SetBody(route).Delete(RoutesRoute)
66+
if err != nil {
67+
return err
68+
}
69+
if resp.StatusCode() != http.StatusNoContent {
70+
return fmt.Errorf("failed to delete route, got %d status code: %s", resp.StatusCode(), string(resp.Body()))
71+
}
72+
return nil
73+
}
74+
75+
// RegisterRecorder registers a recorder on the server
76+
func (c *Client) RegisterRecorder(recorder *Recorder) error {
77+
resp, err := c.restyClient.R().SetBody(recorder).Post(RecorderRoute)
78+
if err != nil {
79+
return err
80+
}
81+
if resp.StatusCode() != http.StatusCreated {
82+
return fmt.Errorf("failed to register recorder, got %d status code: %s", resp.StatusCode(), string(resp.Body()))
83+
}
84+
return nil
85+
}
86+
87+
// Recorders returns all the recorders registered on the server
88+
func (c *Client) Recorders() ([]string, error) {
89+
recorders := []string{}
90+
resp, err := c.restyClient.R().SetResult(&recorders).Get(RecorderRoute)
91+
if err != nil {
92+
return nil, err
93+
}
94+
if resp.StatusCode() != http.StatusOK {
95+
return nil, fmt.Errorf("failed to get recorders, got %d status code: %s", resp.StatusCode(), string(resp.Body()))
96+
}
97+
return recorders, nil
98+
}

parrot/cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func main() {
4747
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
4848
defer cancel()
4949

50-
p, err := parrot.Wake(options...)
50+
p, err := parrot.NewServer(options...)
5151
if err != nil {
5252
return err
5353
}

parrot/examples_test.go

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ import (
77
"os"
88
"time"
99

10-
"github.com/go-resty/resty/v2"
1110
"github.com/rs/zerolog"
1211
"github.com/smartcontractkit/chainlink-testing-framework/parrot"
1312
)
1413

1514
func ExampleServer_internal() {
1615
// Create a new parrot instance with no logging and a custom save file
1716
saveFile := "register_example.json"
18-
p, err := parrot.Wake(parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
17+
p, err := parrot.NewServer(parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
1918
if err != nil {
2019
panic(err)
2120
}
@@ -77,17 +76,14 @@ func ExampleServer_external() {
7776
defer os.Remove(saveFile) // Cleanup the save file for the example
7877

7978
go func() { // Run the parrot server as a separate instance, like in a Docker container
80-
_, err := parrot.Wake(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
79+
_, err := parrot.NewServer(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
8180
if err != nil {
8281
panic(err)
8382
}
8483
}()
8584

86-
// Code that calls the parrot server from another service
87-
// Use resty to make HTTP calls to the parrot server
88-
client := resty.New()
89-
client.SetBaseURL(fmt.Sprintf("http://localhost:%d", port)) // The URL of the parrot server
90-
85+
// Get a client to interact with the parrot server
86+
client := parrot.NewClient(fmt.Sprintf("http://localhost:%d", port))
9187
waitForParrotServerExternal(client, time.Second) // Wait for the parrot server to start
9288

9389
// Register a new route /test that will return a 200 status code with a text/plain response body of "Squawk"
@@ -97,51 +93,43 @@ func ExampleServer_external() {
9793
RawResponseBody: "Squawk",
9894
ResponseStatusCode: http.StatusOK,
9995
}
100-
resp, err := client.R().SetBody(route).Post(parrot.RoutesRoute)
96+
err := client.RegisterRoute(route)
10197
if err != nil {
10298
panic(err)
10399
}
104-
defer resp.RawResponse.Body.Close()
105-
fmt.Println(resp.StatusCode())
100+
fmt.Println("Registered route")
106101

107102
// Get all routes from the parrot server
108-
routes := make([]*parrot.Route, 0)
109-
resp, err = client.R().SetResult(&routes).Get(parrot.RoutesRoute)
103+
routes, err := client.Routes()
110104
if err != nil {
111105
panic(err)
112106
}
113-
defer resp.RawResponse.Body.Close()
114-
fmt.Println(resp.StatusCode())
115-
fmt.Println(len(routes))
107+
fmt.Printf("Found %d routes\n", len(routes))
116108

117109
// Delete the route
118-
resp, err = client.R().SetBody(route).Delete(parrot.RoutesRoute)
110+
err = client.DeleteRoute(route)
119111
if err != nil {
120112
panic(err)
121113
}
122-
defer resp.RawResponse.Body.Close()
123-
fmt.Println(resp.StatusCode())
114+
fmt.Println("Deleted route")
124115

125116
// Get all routes from the parrot server
126-
routes = make([]*parrot.Route, 0)
127-
resp, err = client.R().SetResult(&routes).Get(parrot.RoutesRoute)
117+
routes, err = client.Routes()
128118
if err != nil {
129119
panic(err)
130120
}
131-
defer resp.RawResponse.Body.Close()
132-
fmt.Println(len(routes))
121+
fmt.Printf("Found %d routes\n", len(routes))
133122

134123
// Output:
135-
// 201
136-
// 200
137-
// 1
138-
// 204
139-
// 0
124+
// Registered route
125+
// Found 1 routes
126+
// Deleted route
127+
// Found 0 routes
140128
}
141129

142130
func ExampleRecorder_internal() {
143131
saveFile := "recorder_example.json"
144-
p, err := parrot.Wake(parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
132+
p, err := parrot.NewServer(parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
145133
if err != nil {
146134
panic(err)
147135
}
@@ -220,15 +208,13 @@ func ExampleRecorder_external() {
220208
defer os.Remove(saveFile) // Cleanup the save file for the example
221209

222210
go func() { // Run the parrot server as a separate instance, like in a Docker container
223-
_, err := parrot.Wake(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
211+
_, err := parrot.NewServer(parrot.WithPort(port), parrot.WithLogLevel(zerolog.NoLevel), parrot.WithSaveFile(saveFile))
224212
if err != nil {
225213
panic(err)
226214
}
227215
}()
228216

229-
client := resty.New()
230-
client.SetBaseURL(fmt.Sprintf("http://localhost:%d", port)) // The URL of the parrot server
231-
217+
client := parrot.NewClient(fmt.Sprintf("http://localhost:%d", port))
232218
waitForParrotServerExternal(client, time.Second) // Wait for the parrot server to start
233219

234220
// Register a new route /test that will return a 200 status code with a text/plain response body of "Squawk"
@@ -240,33 +226,36 @@ func ExampleRecorder_external() {
240226
}
241227

242228
// Register the route with the parrot instance
243-
resp, err := client.R().SetBody(route).Post(parrot.RoutesRoute)
229+
err := client.RegisterRoute(route)
244230
if err != nil {
245231
panic(err)
246232
}
247233

248-
// Use the host of the machine your recorder is running on
234+
// Use the recorderHost of the machine your recorder is running on
249235
// This should not be localhost if you are running the parrot server on a different machine
250236
// It should be the public IP address of the machine running your code, so that the parrot can call back to it
251-
host := "localhost"
237+
recorderHost := "localhost"
252238

253239
// Create a new recorder with our host
254-
recorder, err := parrot.NewRecorder(parrot.WithHost(host))
240+
recorder, err := parrot.NewRecorder(parrot.WithHost(recorderHost))
255241
if err != nil {
256242
panic(err)
257243
}
258244

259245
// Register the recorder with the parrot instance
260-
resp, err = client.R().SetBody(recorder).Post(parrot.RecorderRoute)
246+
err = client.RegisterRecorder(recorder)
261247
if err != nil {
262248
panic(err)
263249
}
264-
if resp.StatusCode() != http.StatusCreated {
265-
panic(fmt.Sprintf("failed to register recorder, got %d status code", resp.StatusCode()))
250+
251+
recorders, err := client.Recorders()
252+
if err != nil {
253+
panic(err)
266254
}
255+
fmt.Printf("Found %d recorders\n", len(recorders))
267256

268257
go func() { // Some other service calls the /test route
269-
_, err := client.R().Get("/test")
258+
_, err := client.CallRoute(http.MethodGet, "/test")
270259
if err != nil {
271260
panic(err)
272261
}
@@ -288,25 +277,26 @@ func ExampleRecorder_external() {
288277
}
289278
}
290279
// Output:
280+
// Found 1 recorders
291281
// GET:/test
292282
// GET
293283
// 200
294284
// Squawk
295285
}
296286

297287
// waitForParrotServerExternal checks the parrot server health endpoint until it returns a 200 status code or the timeout is reached
298-
func waitForParrotServerExternal(client *resty.Client, timeoutDur time.Duration) {
288+
func waitForParrotServerExternal(client *parrot.Client, timeoutDur time.Duration) {
299289
ticker := time.NewTicker(50 * time.Millisecond)
300290
defer ticker.Stop()
301291
timeout := time.NewTimer(timeoutDur)
302292
for { // Wait for the parrot server to start
303293
select {
304294
case <-ticker.C:
305-
resp, err := client.R().Get(parrot.HealthRoute)
295+
healthy, err := client.Healthy()
306296
if err != nil {
307-
continue
297+
continue // Ignore errors for health check as the server may not be ready yet
308298
}
309-
if resp.StatusCode() == http.StatusOK {
299+
if healthy {
310300
return
311301
}
312302
case <-timeout.C:

parrot/parrot.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ type SaveFile struct {
8888
Recorders []string `json:"recorders"`
8989
}
9090

91-
// Wake creates a new Parrot server with dynamic route handling
92-
func Wake(options ...ServerOption) (*Server, error) {
91+
// NewServer creates a new Parrot server with dynamic route handling
92+
func NewServer(options ...ServerOption) (*Server, error) {
9393
p := &Server{
9494
port: 0,
9595
saveFileName: "parrot_save.json",
@@ -337,7 +337,7 @@ func (p *Server) Register(route *Route) error {
337337
}
338338
}
339339
numWildcards := strings.Count(route.Path, "*")
340-
if 1 < numWildcards {
340+
if numWildcards > 1 {
341341
return newDynamicError(ErrWildcardPath, fmt.Sprintf("more than 1 wildcard '%s'", route.Path))
342342
}
343343
if numWildcards == 1 && !strings.HasSuffix(route.Path, "*") {

parrot/parrot_benchmark_test.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
func BenchmarkRegisterRoute(b *testing.B) {
1616
saveFile := b.Name() + ".json"
17-
p, err := Wake(WithLogLevel(testLogLevel), WithSaveFile(saveFile))
17+
p, err := NewServer(WithLogLevel(testLogLevel), WithSaveFile(saveFile))
1818
require.NoError(b, err)
1919

2020
defer benchmarkCleanup(b, p, saveFile)
@@ -41,7 +41,7 @@ func BenchmarkRegisterRoute(b *testing.B) {
4141

4242
func BenchmarkRouteResponse(b *testing.B) {
4343
saveFile := b.Name() + ".json"
44-
p, err := Wake(WithLogLevel(testLogLevel), WithSaveFile(saveFile))
44+
p, err := NewServer(WithLogLevel(testLogLevel), WithSaveFile(saveFile))
4545
require.NoError(b, err)
4646

4747
defer benchmarkCleanup(b, p, saveFile)
@@ -77,7 +77,7 @@ func BenchmarkRouteResponse(b *testing.B) {
7777

7878
func BenchmarkGetRoutes(b *testing.B) {
7979
saveFile := b.Name() + ".json"
80-
p, err := Wake(WithLogLevel(testLogLevel), WithSaveFile(saveFile))
80+
p, err := NewServer(WithLogLevel(testLogLevel), WithSaveFile(saveFile))
8181
require.NoError(b, err)
8282

8383
defer benchmarkCleanup(b, p, saveFile)
@@ -104,8 +104,9 @@ func BenchmarkGetRoutes(b *testing.B) {
104104

105105
func BenchmarkSave(b *testing.B) {
106106
var (
107-
routes = []*Route{}
108-
saveFile = "bench_save_routes.json"
107+
routes = []*Route{}
108+
recorders = []string{}
109+
saveFile = "bench_save_routes.json"
109110
)
110111

111112
for i := 0; i < 1000; i++ {
@@ -115,8 +116,9 @@ func BenchmarkSave(b *testing.B) {
115116
RawResponseBody: fmt.Sprintf("Squawk %d", i),
116117
ResponseStatusCode: http.StatusOK,
117118
})
119+
recorders = append(recorders, fmt.Sprintf("http://recorder%d", i))
118120
}
119-
p, err := Wake(WithRoutes(routes), WithLogLevel(testLogLevel), WithSaveFile(saveFile))
121+
p, err := NewServer(WithRoutes(routes), WithRecorders(recorders...), WithLogLevel(testLogLevel), WithSaveFile(saveFile))
120122
require.NoError(b, err)
121123

122124
defer benchmarkCleanup(b, p, saveFile)
@@ -131,8 +133,9 @@ func BenchmarkSave(b *testing.B) {
131133

132134
func BenchmarkLoad(b *testing.B) {
133135
var (
134-
routes = []*Route{}
135-
saveFile = "bench_load_routes.json"
136+
routes = []*Route{}
137+
recorders = []string{}
138+
saveFile = "bench_load_routes.json"
136139
)
137140
b.Cleanup(func() {
138141
os.Remove(saveFile)
@@ -145,8 +148,9 @@ func BenchmarkLoad(b *testing.B) {
145148
RawResponseBody: fmt.Sprintf("Squawk %d", i),
146149
ResponseStatusCode: http.StatusOK,
147150
})
151+
recorders = append(recorders, fmt.Sprintf("http://recorder%d", i))
148152
}
149-
p, err := Wake(WithRoutes(routes), WithLogLevel(zerolog.Disabled), WithSaveFile(saveFile))
153+
p, err := NewServer(WithRoutes(routes), WithRecorders(recorders...), WithLogLevel(zerolog.Disabled), WithSaveFile(saveFile))
150154
require.NoError(b, err, "error waking parrot")
151155

152156
defer benchmarkCleanup(b, p, saveFile)

0 commit comments

Comments
 (0)