Skip to content

Commit 60325bc

Browse files
authored
Merge pull request #29 from messagebird/voice-fixes
Various voice fixes and integration tests
2 parents 5a6ec99 + db3da20 commit 60325bc

15 files changed

+274
-54
lines changed

client.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"bytes"
1414
"encoding/json"
1515
"errors"
16+
"fmt"
1617
"io/ioutil"
1718
"log"
1819
"net/http"
@@ -67,7 +68,10 @@ func New(AccessKey string) *Client {
6768

6869
// Request is for internal use only and unstable.
6970
func (c *Client) Request(v interface{}, method, path string, data interface{}) error {
70-
uri, err := url.Parse(Endpoint + "/" + path)
71+
if !strings.HasPrefix(path, "https://") && !strings.HasPrefix(path, "http://") {
72+
path = fmt.Sprintf("%s/%s", Endpoint, path)
73+
}
74+
uri, err := url.Parse(path)
7175
if err != nil {
7276
return err
7377
}
@@ -119,17 +123,20 @@ func (c *Client) Request(v interface{}, method, path string, data interface{}) e
119123
if response.StatusCode == 500 {
120124
return ErrUnexpectedResponse
121125
}
122-
123-
if err = json.Unmarshal(responseBody, &v); err != nil {
124-
return err
125-
}
126-
127126
// Status codes 200 and 201 are indicative of being able to convert the
128127
// response body to the struct that was specified.
129128
if response.StatusCode == 200 || response.StatusCode == 201 {
129+
if err := json.Unmarshal(responseBody, &v); err != nil {
130+
return fmt.Errorf("could not decode response JSON, %s: %v", string(responseBody), err)
131+
}
130132
return nil
131133
}
132134

135+
// We're dealing with an API error here. try to decode it, but don't do
136+
// anything with the error. This is because not all values of `v` have
137+
// `Error` properties and decoding could fail.
138+
json.Unmarshal(responseBody, &v)
139+
133140
// Anything else than a 200/201/500 should be a JSON error.
134141
return ErrResponse
135142
}

voice/call.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,17 @@ func (call *Call) UnmarshalJSON(data []byte) error {
7676
}
7777
createdAt, err := time.Parse(time.RFC3339, raw.CreatedAt)
7878
if err != nil {
79-
return fmt.Errorf("unable to parse CallFlow CreatedAt: %v", err)
79+
return fmt.Errorf("unable to parse Call CreatedAt: %v", err)
8080
}
8181
updatedAt, err := time.Parse(time.RFC3339, raw.UpdatedAt)
8282
if err != nil {
83-
return fmt.Errorf("unable to parse CallFlow UpdatedAt: %v", err)
83+
return fmt.Errorf("unable to parse Call UpdatedAt: %v", err)
8484
}
8585
var endedAt *time.Time
8686
if raw.EndedAt != "" {
8787
eat, err := time.Parse(time.RFC3339, raw.EndedAt)
8888
if err != nil {
89-
return fmt.Errorf("unable to parse CallFlow EndedAt: %v", err)
89+
return fmt.Errorf("unable to parse Call EndedAt: %v", err)
9090
}
9191
endedAt = &eat
9292
}
@@ -107,14 +107,18 @@ func (call *Call) UnmarshalJSON(data []byte) error {
107107
//
108108
// An error is returned if no such call flow exists or is accessible.
109109
func CallByID(client *messagebird.Client, id string) (*Call, error) {
110-
call := &Call{}
111-
err := client.Request(call, http.MethodGet, "calls/"+id, nil)
112-
return call, err
110+
var resp struct {
111+
Data []Call `json:"data"`
112+
}
113+
if err := client.Request(&resp, http.MethodGet, apiRoot+"/calls/"+id, nil); err != nil {
114+
return nil, err
115+
}
116+
return &resp.Data[0], nil
113117
}
114118

115119
// Calls returns a Paginator which iterates over all Calls.
116120
func Calls(client *messagebird.Client) *Paginator {
117-
return newPaginator(client, "calls/", reflect.TypeOf(Call{}))
121+
return newPaginator(client, apiRoot+"/calls/", reflect.TypeOf(Call{}))
118122
}
119123

120124
// InitiateCall initiates an outbound call.
@@ -140,19 +144,23 @@ func InitiateCall(client *messagebird.Client, source, destination string, callfl
140144
body.Webhook.URL = webhook.URL
141145
body.Webhook.Token = webhook.Token
142146
}
143-
call := &Call{}
144-
err := client.Request(call, http.MethodPost, "calls/", body)
145-
return call, err
147+
var resp struct {
148+
Data []Call `json:"data"`
149+
}
150+
if err := client.Request(&resp, http.MethodPost, apiRoot+"/calls", body); err != nil {
151+
return nil, err
152+
}
153+
return &resp.Data[0], nil
146154
}
147155

148156
// Delete deletes the Call.
149157
//
150158
// If the call is in progress, it hangs up all legs.
151159
func (call *Call) Delete(client *messagebird.Client) error {
152-
return client.Request(nil, http.MethodDelete, "calls/"+call.ID, nil)
160+
return client.Request(nil, http.MethodDelete, apiRoot+"/calls/"+call.ID, nil)
153161
}
154162

155163
// Legs returns a paginator over all Legs associated with a call.
156164
func (call *Call) Legs(client *messagebird.Client) *Paginator {
157-
return newPaginator(client, fmt.Sprintf("calls/%s/legs", call.ID), reflect.TypeOf(Leg{}))
165+
return newPaginator(client, fmt.Sprintf("%s/calls/%s/legs", apiRoot, call.ID), reflect.TypeOf(Leg{}))
158166
}

voice/call_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package voice
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestInitiateCall(t *testing.T) {
9+
client, ok := testClient(t)
10+
if !ok {
11+
t.SkipNow()
12+
}
13+
14+
source, destination := "31000000000", "31000000000"
15+
callflow := CallFlow{
16+
Title: "Say test",
17+
Steps: []CallFlowStep{
18+
&CallFlowSayStep{
19+
Voice: "male",
20+
Payload: "You are about to experience a great adventure which reaches from the inner mind to the outer limits",
21+
Language: "en-US",
22+
},
23+
&CallFlowPauseStep{
24+
Length: time.Second,
25+
},
26+
&CallFlowHangupStep{},
27+
},
28+
}
29+
_, err := InitiateCall(client, source, destination, callflow, nil)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
}
34+
35+
func TestCallByID(t *testing.T) {
36+
client, ok := testClient(t)
37+
if !ok {
38+
t.SkipNow()
39+
}
40+
41+
source, destination := "31000000000", "31000000000"
42+
callflow := CallFlow{
43+
Title: "Say test",
44+
Steps: []CallFlowStep{
45+
&CallFlowSayStep{
46+
Voice: "male",
47+
Payload: "You are about to experience a great adventure which reaches from the inner mind to the outer limits",
48+
Language: "en-US",
49+
},
50+
&CallFlowPauseStep{
51+
Length: time.Second,
52+
},
53+
&CallFlowHangupStep{},
54+
},
55+
}
56+
call, err := InitiateCall(client, source, destination, callflow, nil)
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
61+
time.Sleep(time.Second)
62+
fetchedCall, err := CallByID(client, call.ID)
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
if fetchedCall.Source != call.Source {
67+
t.Fatalf("mismatched source: exp %q, got %q", call.Source, fetchedCall.Source)
68+
}
69+
}

voice/callflow.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,18 @@ func (callflow *CallFlow) UnmarshalJSON(data []byte) error {
105105
//
106106
// An error is returned if no such call flow exists or is accessible.
107107
func CallFlowByID(client *messagebird.Client, id string) (*CallFlow, error) {
108-
callflow := &CallFlow{}
109-
err := client.Request(callflow, http.MethodGet, "call-flows/"+id, nil)
110-
return callflow, err
108+
var data struct {
109+
Data []CallFlow `json:"data"`
110+
}
111+
if err := client.Request(&data, http.MethodGet, apiRoot+"/call-flows/"+id, nil); err != nil {
112+
return nil, err
113+
}
114+
return &data.Data[0], nil
111115
}
112116

113117
// CallFlows returns a Paginator which iterates over all CallFlows.
114118
func CallFlows(client *messagebird.Client) *Paginator {
115-
return newPaginator(client, "call-flows/", reflect.TypeOf(CallFlow{}))
119+
return newPaginator(client, apiRoot+"/call-flows/", reflect.TypeOf(CallFlow{}))
116120
}
117121

118122
// Create creates the callflow remotely.
@@ -122,7 +126,7 @@ func (callflow *CallFlow) Create(client *messagebird.Client) error {
122126
var data struct {
123127
Data []CallFlow `json:"data"`
124128
}
125-
if err := client.Request(&data, http.MethodPost, "call-flows/", callflow); err != nil {
129+
if err := client.Request(&data, http.MethodPost, apiRoot+"/call-flows/", callflow); err != nil {
126130
return err
127131
}
128132
*callflow = data.Data[0]
@@ -136,7 +140,7 @@ func (callflow *CallFlow) Update(client *messagebird.Client) error {
136140
var data struct {
137141
Data []CallFlow `json:"data"`
138142
}
139-
if err := client.Request(&data, http.MethodPut, "call-flows/"+callflow.ID, callflow); err != nil {
143+
if err := client.Request(&data, http.MethodPut, apiRoot+"/call-flows/"+callflow.ID, callflow); err != nil {
140144
return err
141145
}
142146
*callflow = data.Data[0]
@@ -145,7 +149,7 @@ func (callflow *CallFlow) Update(client *messagebird.Client) error {
145149

146150
// Delete deletes the CallFlow.
147151
func (callflow *CallFlow) Delete(client *messagebird.Client) error {
148-
return client.Request(nil, http.MethodDelete, "call-flows/"+callflow.ID, nil)
152+
return client.Request(nil, http.MethodDelete, apiRoot+"/call-flows/"+callflow.ID, nil)
149153
}
150154

151155
// A CallFlowStep is a single step that can be taken in a callflow.

voice/callflow_test.go

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package voice
22

33
import (
44
"encoding/json"
5-
"net/http"
65
"reflect"
76
"testing"
87
"time"
@@ -131,25 +130,86 @@ func TestCallFlowJSONUnmarshal(t *testing.T) {
131130
}
132131

133132
func TestCreateCallFlow(t *testing.T) {
134-
mbClient, stop := testClient(http.StatusOK, []byte(`{
135-
"data": [
136-
{
137-
"id": "the-id",
138-
"title": "the-title",
139-
"createdAt": "2018-01-29T13:46:06Z",
140-
"updatedAt": "2018-01-30T16:00:34Z"
141-
}
142-
]
143-
}`))
144-
defer stop()
145-
newCf := &CallFlow{}
133+
mbClient, ok := testClient(t)
134+
if !ok {
135+
t.SkipNow()
136+
}
137+
138+
newCf := &CallFlow{
139+
Title: "the-title",
140+
Steps: []CallFlowStep{
141+
&CallFlowSayStep{
142+
Payload: "Hello",
143+
Language: "en-US",
144+
Voice: "male",
145+
},
146+
&CallFlowPauseStep{
147+
Length: time.Second,
148+
},
149+
},
150+
}
146151
if err := newCf.Create(mbClient); err != nil {
147152
t.Fatal(err)
148153
}
149-
if newCf.ID != "the-id" {
150-
t.Fatalf("Unexpected ID: %q", newCf.ID)
151-
}
152154
if newCf.Title != "the-title" {
153155
t.Fatalf("Unexpected Title: %q", newCf.Title)
154156
}
157+
if len(newCf.Steps) != 2 {
158+
t.Fatalf("Unexpected Title: %q", newCf.Title)
159+
}
160+
}
161+
162+
func TestCallFlowByID(t *testing.T) {
163+
mbClient, ok := testClient(t)
164+
if !ok {
165+
t.SkipNow()
166+
}
167+
168+
newCf := &CallFlow{
169+
Title: "the-title",
170+
Steps: []CallFlowStep{
171+
&CallFlowHangupStep{},
172+
},
173+
}
174+
if err := newCf.Create(mbClient); err != nil {
175+
t.Fatal(err)
176+
}
177+
178+
fetchedCf, err := CallFlowByID(mbClient, newCf.ID)
179+
if err != nil {
180+
t.Fatal(err)
181+
}
182+
if fetchedCf.ID != newCf.ID {
183+
t.Fatalf("mismatched fetched IDs: exp %q, got %q", newCf.ID, fetchedCf.ID)
184+
}
185+
}
186+
187+
func TestCallFlowList(t *testing.T) {
188+
mbClient, ok := testClient(t)
189+
if !ok {
190+
t.SkipNow()
191+
}
192+
193+
for _, c := range []string{"foo", "bar", "baz"} {
194+
newCf := &CallFlow{
195+
Title: c,
196+
Steps: []CallFlowStep{
197+
&CallFlowHangupStep{},
198+
},
199+
}
200+
if err := newCf.Create(mbClient); err != nil {
201+
t.Fatal(err)
202+
}
203+
}
204+
205+
i := 0
206+
for cf := range CallFlows(mbClient).Stream() {
207+
if err, ok := cf.(error); ok {
208+
t.Fatal(err)
209+
}
210+
i++
211+
}
212+
if i == 0 {
213+
t.Fatal("no callflows were fetched")
214+
}
155215
}

voice/leg.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,5 @@ func (leg *Leg) UnmarshalJSON(data []byte) error {
150150

151151
// Recordings retrieves the Recording objects associated with a leg.
152152
func (leg *Leg) Recordings(client *messagebird.Client) *Paginator {
153-
return newPaginator(client, fmt.Sprintf("calls/%s/legs/%s/recordings", leg.CallID, leg.ID), reflect.TypeOf(Recording{}))
153+
return newPaginator(client, fmt.Sprintf("%s/calls/%s/legs/%s/recordings", apiRoot, leg.CallID, leg.ID), reflect.TypeOf(Recording{}))
154154
}

voice/main_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/messagebird/go-rest-api"
1313
)
1414

15-
func testClient(status int, body []byte) (*messagebird.Client, func()) {
15+
func testRequest(status int, body []byte) (*messagebird.Client, func()) {
1616
mbServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1717
w.Header().Set("Content-Type", "application/json")
1818
w.WriteHeader(status)
@@ -33,3 +33,25 @@ func testClient(status int, body []byte) (*messagebird.Client, func()) {
3333
}
3434
return mbClient, func() { mbServer.Close() }
3535
}
36+
37+
func testClient(t *testing.T) (*messagebird.Client, bool) {
38+
key, ok := os.LookupEnv("MB_TEST_KEY")
39+
if !ok {
40+
return nil, false
41+
}
42+
client := &messagebird.Client{
43+
AccessKey: key,
44+
HTTPClient: &http.Client{},
45+
DebugLog: log.New(testWriter{T: t}, "", 0),
46+
}
47+
return client, true
48+
}
49+
50+
type testWriter struct {
51+
T *testing.T
52+
}
53+
54+
func (tw testWriter) Write(b []byte) (int, error) {
55+
tw.T.Logf("%s", b)
56+
return len(b), nil
57+
}

voice/paginator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ func TestPaginatorStream(t *testing.T) {
1010
type myStruct struct {
1111
Val int
1212
}
13-
mbClient, stop := testClient(http.StatusOK, []byte(`{
13+
mbClient, stop := testRequest(http.StatusOK, []byte(`{
1414
"data": [
1515
{ "Val": 1 },
1616
{ "Val": 2 },

0 commit comments

Comments
 (0)