Skip to content

Commit 30ee7e6

Browse files
authored
EntityClient improvements (#74)
* Added WithContext(ctx) to EntityClient to attach a context to the outbound HTTP request * Added Get(id) to EntityClient to fetch an Entity by ID. This is an atypical use case for CLI usage and is not included in the CLI, but can be helpful for integration tests
1 parent 959c0f7 commit 30ee7e6

File tree

2 files changed

+216
-20
lines changed

2 files changed

+216
-20
lines changed

client_test.go

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
package etre_test
44

55
import (
6+
"context"
67
"encoding/json"
7-
"io/ioutil"
8+
"io"
89
"net/http"
910
"net/http/httptest"
1011
"net/url"
@@ -34,7 +35,10 @@ var (
3435
respError *etre.Error // if respData is nil
3536
respStatusCode int
3637
)
37-
var httpClient = &http.Client{}
38+
var httpRT = &rt{}
39+
var httpClient = &http.Client{
40+
Transport: httpRT,
41+
}
3842

3943
func init() {
4044
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -44,7 +48,7 @@ func init() {
4448

4549
if r.Method == "POST" || r.Method == "PUT" {
4650
var err error
47-
gotBody, err = ioutil.ReadAll(r.Body)
51+
gotBody, err = io.ReadAll(r.Body)
4852
if err != nil {
4953
panic(err.Error())
5054
}
@@ -146,15 +150,17 @@ func TestQueryOK(t *testing.T) {
146150
ec := etre.NewEntityClient("node", ts.URL, httpClient)
147151

148152
// Normal query that returns status code 200 and respData
153+
ctx := testContext()
149154
query := "x=y"
150-
got, err := ec.Query(query, etre.QueryFilter{})
155+
got, err := ec.WithContext(ctx).Query(query, etre.QueryFilter{})
151156
require.NoError(t, err)
152157

153158
// Verify call and response
154159
assert.Equal(t, "GET", gotMethod)
155160
assert.Equal(t, etre.API_ROOT+"/entities/node", gotPath)
156161
assert.Equal(t, "query="+query, gotQuery)
157162
assert.Equal(t, got, respData)
163+
assert.Equal(t, ctx, httpRT.gotCtx)
158164
}
159165

160166
func TestQueryNoResults(t *testing.T) {
@@ -206,6 +212,82 @@ func TestQueryUnhandledError(t *testing.T) {
206212
assert.Nil(t, got)
207213
}
208214

215+
// //////////////////////////////////////////////////////////////////////////
216+
// Get
217+
// //////////////////////////////////////////////////////////////////////////
218+
func TestGetOK(t *testing.T) {
219+
setup(t)
220+
221+
// Set global vars used by httptest.Server
222+
respData = etre.Entity{
223+
"_id": "abc",
224+
"hostname": "localhost",
225+
}
226+
227+
ec := etre.NewEntityClient("node", ts.URL, httpClient)
228+
229+
// Normal get that returns status code 200 and respData
230+
ctx := testContext()
231+
got, err := ec.WithContext(ctx).Get("abc")
232+
require.NoError(t, err)
233+
234+
// Verify call and response
235+
assert.Equal(t, "GET", gotMethod)
236+
assert.Equal(t, etre.API_ROOT+"/entity/node/abc", gotPath)
237+
assert.Equal(t, got, respData)
238+
assert.Equal(t, ctx, httpRT.gotCtx)
239+
}
240+
241+
func TestGetHandledError(t *testing.T) {
242+
// Test that client returns error on API error and no entity
243+
setup(t)
244+
245+
// Set global vars used by httptest.Server
246+
respStatusCode = http.StatusInternalServerError
247+
respError = &etre.Error{
248+
Type: "fake_error",
249+
Message: "this is a fake error",
250+
}
251+
252+
ec := etre.NewEntityClient("node", ts.URL, httpClient)
253+
got, err := ec.Get("abc")
254+
require.Error(t, err)
255+
assert.Contains(t, err.Error(), respError.Type)
256+
assert.Nil(t, got)
257+
}
258+
259+
func TestGetUnhandledError(t *testing.T) {
260+
// Like TestGetHandledError above, but simulating a more severe error,
261+
// like a panic, that makes the API _not_ return an etre.Error. The client
262+
// should handle this and still return an error.
263+
setup(t)
264+
265+
// Set global vars used by httptest.Server
266+
respStatusCode = http.StatusInternalServerError
267+
268+
ec := etre.NewEntityClient("node", ts.URL, httpClient)
269+
got, err := ec.Get("abc")
270+
require.Error(t, err)
271+
assert.Contains(t, err.Error(), "no response")
272+
assert.Nil(t, got)
273+
}
274+
275+
func TestGetNotFound(t *testing.T) {
276+
// Like TestGetHandledError above, but simulating a more severe error,
277+
// like a panic, that makes the API _not_ return an etre.Error. The client
278+
// should handle this and still return an error.
279+
setup(t)
280+
281+
// Set global vars used by httptest.Server
282+
respStatusCode = http.StatusNotFound
283+
284+
ec := etre.NewEntityClient("node", ts.URL, httpClient)
285+
got, err := ec.Get("abc")
286+
require.Error(t, err)
287+
assert.Contains(t, err.Error(), "not found")
288+
assert.Nil(t, got)
289+
}
290+
209291
// //////////////////////////////////////////////////////////////////////////
210292
// Insert
211293
// //////////////////////////////////////////////////////////////////////////
@@ -233,14 +315,16 @@ func TestInsertOK(t *testing.T) {
233315
"foo": "bar",
234316
},
235317
}
236-
got, err := ec.Insert(entities)
318+
ctx := testContext()
319+
got, err := ec.WithContext(ctx).Insert(entities)
237320
require.NoError(t, err)
238321

239322
// Verify call and response
240323
assert.Equal(t, "POST", gotMethod)
241324
assert.Equal(t, etre.API_ROOT+"/entities/node", gotPath)
242325
assert.Empty(t, gotQuery)
243326
assert.Equal(t, respData, got)
327+
assert.Equal(t, ctx, httpRT.gotCtx)
244328
}
245329

246330
func TestInsertAPIError(t *testing.T) {
@@ -323,14 +407,16 @@ func TestUpdateOK(t *testing.T) {
323407
entity := etre.Entity{
324408
"foo": "bar", // patch foo:foo -> for:bar
325409
}
326-
got, err := ec.Update("foo=bar", entity)
410+
ctx := testContext()
411+
got, err := ec.WithContext(ctx).Update("foo=bar", entity)
327412
require.NoError(t, err)
328413

329414
// Verify call and response
330415
assert.Equal(t, "PUT", gotMethod)
331416
assert.Equal(t, etre.API_ROOT+"/entities/node", gotPath)
332417
assert.Equal(t, "query=foo=bar", gotQuery)
333418
assert.Equal(t, respData, got)
419+
assert.Equal(t, ctx, httpRT.gotCtx)
334420
}
335421

336422
func TestUpdateAPIError(t *testing.T) {
@@ -429,14 +515,16 @@ func TestDeleteOK(t *testing.T) {
429515

430516
// Normal delete that returns status code 200 and a write result
431517
query := "foo=bar"
432-
got, err := ec.Delete(query)
518+
ctx := testContext()
519+
got, err := ec.WithContext(ctx).Delete(query)
433520
require.NoError(t, err)
434521

435522
// Verify call and response
436523
assert.Equal(t, "DELETE", gotMethod)
437524
assert.Equal(t, etre.API_ROOT+"/entities/node", gotPath)
438525
assert.Equal(t, "query="+query, gotQuery)
439526
assert.Equal(t, respData, got)
527+
assert.Equal(t, ctx, httpRT.gotCtx)
440528
}
441529

442530
func TestDeleteWithSet(t *testing.T) {
@@ -475,7 +563,6 @@ func TestDeleteWithSet(t *testing.T) {
475563
assert.Equal(t, etre.API_ROOT+"/entities/node", gotPath)
476564
assert.Equal(t, "query="+query+"&setId=setid&setOp=setop&setSize=3", gotQuery)
477565
assert.Equal(t, respData, got)
478-
479566
}
480567

481568
// //////////////////////////////////////////////////////////////////////////
@@ -500,13 +587,15 @@ func TestDeleteOneOK(t *testing.T) {
500587

501588
ec := etre.NewEntityClient("node", ts.URL, httpClient)
502589

503-
got, err := ec.DeleteOne("abc")
590+
ctx := testContext()
591+
got, err := ec.WithContext(ctx).DeleteOne("abc")
504592
require.NoError(t, err)
505593

506594
assert.Equal(t, "DELETE", gotMethod)
507595
assert.Equal(t, etre.API_ROOT+"/entity/node/abc", gotPath)
508596
assert.Empty(t, gotQuery)
509597
assert.Equal(t, respData, got)
598+
assert.Equal(t, ctx, httpRT.gotCtx)
510599
}
511600

512601
func TestDeleteOneWithSet(t *testing.T) {
@@ -554,12 +643,14 @@ func TestLabelsOK(t *testing.T) {
554643

555644
ec := etre.NewEntityClient("node", ts.URL, httpClient)
556645

557-
got, err := ec.Labels("abc")
646+
ctx := testContext()
647+
got, err := ec.WithContext(ctx).Labels("abc")
558648
require.NoError(t, err)
559649
assert.Equal(t, "GET", gotMethod)
560650
assert.Equal(t, etre.API_ROOT+"/entity/node/abc/labels", gotPath)
561651
assert.Empty(t, gotQuery)
562652
assert.Equal(t, respData, got)
653+
assert.Equal(t, ctx, httpRT.gotCtx)
563654
}
564655

565656
func TestDeleteLabelOK(t *testing.T) {
@@ -579,12 +670,14 @@ func TestDeleteLabelOK(t *testing.T) {
579670

580671
ec := etre.NewEntityClient("node", ts.URL, httpClient)
581672

582-
got, err := ec.DeleteLabel("abc", "foo")
673+
ctx := testContext()
674+
got, err := ec.WithContext(ctx).DeleteLabel("abc", "foo")
583675
require.NoError(t, err)
584676
assert.Equal(t, "DELETE", gotMethod)
585677
assert.Equal(t, etre.API_ROOT+"/entity/node/abc/labels/foo", gotPath)
586678
assert.Empty(t, gotQuery)
587679
assert.Equal(t, respData, got)
680+
assert.Equal(t, ctx, httpRT.gotCtx)
588681
}
589682

590683
// //////////////////////////////////////////////////////////////////////////
@@ -771,3 +864,31 @@ func TestCDCClient(t *testing.T) {
771864
gotError := ec.Error().Error()
772865
assert.Contains(t, gotError, "fake error")
773866
}
867+
868+
func TestWithContext(t *testing.T) {
869+
ctx1 := context.Background()
870+
ctx2 := context.WithValue(ctx1, "key", "value")
871+
872+
// client w/o context should return context.Background()
873+
client1 := etre.NewEntityClient("node", ts.URL, httpClient)
874+
require.NotNil(t, client1.Context())
875+
assert.Equal(t, ctx1, client1.Context())
876+
877+
// set the context. should not change client1's context
878+
client2 := client1.WithContext(ctx2)
879+
assert.Equal(t, ctx2, client2.Context())
880+
assert.Equal(t, ctx1, client1.Context())
881+
}
882+
883+
func testContext() context.Context {
884+
return context.WithValue(context.Background(), "key", "test-context-"+time.Now().String())
885+
}
886+
887+
type rt struct {
888+
gotCtx context.Context
889+
}
890+
891+
func (t *rt) RoundTrip(r *http.Request) (*http.Response, error) {
892+
t.gotCtx = r.Context()
893+
return http.DefaultTransport.RoundTrip(r)
894+
}

0 commit comments

Comments
 (0)