Skip to content

Commit 2e57525

Browse files
authored
misc: move webhook cluster test (#195)
* Extract out webhook cluster test from workflow
1 parent 1b3f333 commit 2e57525

File tree

5 files changed

+159
-136
lines changed

5 files changed

+159
-136
lines changed

.github/workflows/test-and-deploy.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ jobs:
4949
TWILIO_API_SECRET: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY_SECRET }}
5050
TWILIO_FROM_NUMBER: ${{ secrets.TWILIO_FROM_NUMBER }}
5151
TWILIO_TO_NUMBER: ${{ secrets.TWILIO_TO_NUMBER }}
52-
TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
5352
run: make cluster-test
5453

5554
- name: Run Test Coverage

Makefile

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ test-docker:
1313
docker build -t twilio/twilio-go .
1414
docker run twilio/twilio-go go test -race ./...
1515

16-
test-docker:
17-
docker build -t twilio/twilio-go .
18-
docker run twilio/twilio-go go test ./...
19-
2016
cluster-test:
2117
go test -race --tags=cluster
2218

19+
webhook-cluster-test:
20+
go test -race --tags=webhook_cluster
21+
2322
goimports:
2423
go install golang.org/x/tools/cmd/goimports@latest
2524
goimports -w .

PULL_REQUEST_TEMPLATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Please enter each Issue number you are resolving in your PR after one of the fol
1212
e.g.
1313
Fixes #1
1414
Closes #2
15+
16+
Note: If you made changes to the Request Validation logic, please run `make webhook-cluster-test` locally and ensure all test cases pass
1517
-->
1618

1719
# Fixes #

cluster_test.go

Lines changed: 2 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,12 @@
44
package twilio
55

66
import (
7-
"encoding/json"
8-
"fmt"
9-
"net/http"
10-
"os"
11-
"testing"
12-
"time"
13-
14-
"github.com/localtunnel/go-localtunnel"
157
"github.com/stretchr/testify/assert"
16-
twilio "github.com/twilio/twilio-go/client"
178
Api "github.com/twilio/twilio-go/rest/api/v2010"
189
ChatV2 "github.com/twilio/twilio-go/rest/chat/v2"
1910
EventsV1 "github.com/twilio/twilio-go/rest/events/v1"
20-
StudioV2 "github.com/twilio/twilio-go/rest/studio/v2"
11+
"os"
12+
"testing"
2113
)
2214

2315
var from string
@@ -127,127 +119,6 @@ func TestListParams(t *testing.T) {
127119
assert.Nil(t, err)
128120
}
129121

130-
func createValidationServer(channel chan bool) *http.Server {
131-
server := &http.Server{}
132-
server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
133-
url := r.Header["X-Forwarded-Proto"][0] + "://" + r.Header["X-Forwarded-Host"][0] + r.URL.RequestURI()
134-
signatureHeader := r.Header["X-Twilio-Signature"]
135-
r.ParseForm()
136-
params := make(map[string]string)
137-
for k, v := range r.PostForm {
138-
params[k] = v[0]
139-
}
140-
requestValidator := twilio.NewRequestValidator(os.Getenv("TWILIO_AUTH_TOKEN"))
141-
if len(signatureHeader) != 0 {
142-
channel <- requestValidator.Validate(url, params, r.Header["X-Twilio-Signature"][0])
143-
} else {
144-
channel <- false
145-
}
146-
})
147-
return server
148-
}
149-
150-
func createStudioFlowParams(url string, method string) *StudioV2.CreateFlowParams {
151-
jsonStr := fmt.Sprintf(`{
152-
"description": "Studio Flow",
153-
"states": [
154-
{
155-
"name": "Trigger",
156-
"type": "trigger",
157-
"transitions": [
158-
{
159-
"next": "httpRequest",
160-
"event": "incomingRequest"
161-
}
162-
],
163-
"properties": {
164-
}
165-
},
166-
{
167-
"name": "httpRequest",
168-
"type": "make-http-request",
169-
"transitions": [],
170-
"properties": {
171-
"method": "%s",
172-
"content_type": "application/x-www-form-urlencoded;charset=utf-8",
173-
"url": "%s"
174-
}
175-
}
176-
],
177-
"initial_state": "Trigger",
178-
"flags": {
179-
"allow_concurrent_calls": true
180-
}
181-
}`, method, url)
182-
183-
var definition interface{}
184-
_ = json.Unmarshal([]byte(jsonStr), &definition)
185-
186-
params := &StudioV2.CreateFlowParams{
187-
Definition: &definition,
188-
}
189-
params.SetFriendlyName("Go Cluster Test Flow")
190-
params.SetStatus("published")
191-
return params
192-
}
193-
194-
func createStudioExecutionParams() *StudioV2.CreateExecutionParams {
195-
executionParams := &StudioV2.CreateExecutionParams{}
196-
executionParams.SetTo("To")
197-
executionParams.SetFrom("From")
198-
return executionParams
199-
}
200-
201-
func executeFlow(t *testing.T, flowSid string) {
202-
_, exeErr := testClient.StudioV2.CreateExecution(flowSid, createStudioExecutionParams())
203-
if exeErr != nil {
204-
t.Fatal("Error with Studio Execution Creation: ", exeErr)
205-
}
206-
}
207-
208-
func requestValidation(t *testing.T, method string) {
209-
//Invoke Localtunnel
210-
listener, ltErr := localtunnel.Listen(localtunnel.Options{})
211-
if ltErr != nil {
212-
t.Fatal("Error with Localtunnel: ", ltErr)
213-
}
214-
//Create Validation Server & Listen
215-
channel := make(chan bool)
216-
server := createValidationServer(channel)
217-
go server.Serve(listener)
218-
219-
//Extra time for server to set up
220-
time.Sleep(1 * time.Second)
221-
222-
//Create Studio Flow
223-
params := createStudioFlowParams(listener.URL(), method)
224-
resp, flowErr := testClient.StudioV2.CreateFlow(params)
225-
if flowErr != nil {
226-
t.Fatal("Error with Studio Flow Creation: ", flowErr)
227-
}
228-
flowSid := *resp.Sid
229-
executeFlow(t, flowSid)
230-
231-
//Await for Request Validation
232-
afterCh := time.After(5 * time.Second)
233-
select {
234-
case validate := <-channel:
235-
assert.True(t, validate)
236-
case <-afterCh:
237-
t.Fatal("No request was sent to validation server")
238-
}
239-
defer testClient.StudioV2.DeleteFlow(flowSid)
240-
defer server.Close()
241-
}
242-
243-
func TestRequestValidation_GETMethod(t *testing.T) {
244-
requestValidation(t, "GET")
245-
}
246-
247-
func TestRequestValidation_POSTMethod(t *testing.T) {
248-
requestValidation(t, "POST")
249-
}
250-
251122
func TestListingAvailableNumber(t *testing.T) {
252123
params := &Api.ListAvailablePhoneNumberTollFreeParams{}
253124
params.SetLimit(2)

webhook_cluster_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//go:build webhook_cluster
2+
// +build webhook_cluster
3+
4+
package twilio
5+
6+
import (
7+
"encoding/json"
8+
"fmt"
9+
"net/http"
10+
"os"
11+
"testing"
12+
"time"
13+
14+
"github.com/localtunnel/go-localtunnel"
15+
"github.com/stretchr/testify/assert"
16+
twilio "github.com/twilio/twilio-go/client"
17+
StudioV2 "github.com/twilio/twilio-go/rest/studio/v2"
18+
)
19+
20+
var testClient *RestClient
21+
var authToken string
22+
23+
func TestMain(m *testing.M) {
24+
var apiKey = os.Getenv("TWILIO_API_KEY")
25+
var secret = os.Getenv("TWILIO_API_SECRET")
26+
var accountSid = os.Getenv("TWILIO_ACCOUNT_SID")
27+
authToken = os.Getenv("TWILIO_AUTH_TOKEN")
28+
testClient = NewRestClientWithParams(ClientParams{apiKey, secret, accountSid, nil})
29+
ret := m.Run()
30+
os.Exit(ret)
31+
}
32+
33+
func createValidationServer(channel chan bool) *http.Server {
34+
server := &http.Server{}
35+
server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36+
url := r.Header["X-Forwarded-Proto"][0] + "://" + r.Header["X-Forwarded-Host"][0] + r.URL.RequestURI()
37+
signatureHeader := r.Header["X-Twilio-Signature"]
38+
r.ParseForm()
39+
params := make(map[string]string)
40+
for k, v := range r.PostForm {
41+
params[k] = v[0]
42+
}
43+
requestValidator := twilio.NewRequestValidator(authToken)
44+
if len(signatureHeader) != 0 {
45+
channel <- requestValidator.Validate(url, params, r.Header["X-Twilio-Signature"][0])
46+
} else {
47+
channel <- false
48+
}
49+
})
50+
return server
51+
}
52+
53+
func createStudioFlowParams(url string, method string) *StudioV2.CreateFlowParams {
54+
jsonStr := fmt.Sprintf(`{
55+
"description": "Studio Flow",
56+
"states": [
57+
{
58+
"name": "Trigger",
59+
"type": "trigger",
60+
"transitions": [
61+
{
62+
"next": "httpRequest",
63+
"event": "incomingRequest"
64+
}
65+
],
66+
"properties": {
67+
}
68+
},
69+
{
70+
"name": "httpRequest",
71+
"type": "make-http-request",
72+
"transitions": [],
73+
"properties": {
74+
"method": "%s",
75+
"content_type": "application/x-www-form-urlencoded;charset=utf-8",
76+
"url": "%s"
77+
}
78+
}
79+
],
80+
"initial_state": "Trigger",
81+
"flags": {
82+
"allow_concurrent_calls": true
83+
}
84+
}`, method, url)
85+
86+
var definition interface{}
87+
_ = json.Unmarshal([]byte(jsonStr), &definition)
88+
89+
params := &StudioV2.CreateFlowParams{
90+
Definition: &definition,
91+
}
92+
params.SetFriendlyName("Go Cluster Test Flow")
93+
params.SetStatus("published")
94+
return params
95+
}
96+
97+
func createStudioExecutionParams() *StudioV2.CreateExecutionParams {
98+
executionParams := &StudioV2.CreateExecutionParams{}
99+
executionParams.SetTo("To")
100+
executionParams.SetFrom("From")
101+
return executionParams
102+
}
103+
104+
func executeFlow(t *testing.T, flowSid string) {
105+
_, exeErr := testClient.StudioV2.CreateExecution(flowSid, createStudioExecutionParams())
106+
if exeErr != nil {
107+
t.Fatal("Error with Studio Execution Creation: ", exeErr)
108+
}
109+
}
110+
111+
func requestValidation(t *testing.T, method string) {
112+
//Invoke Localtunnel
113+
listener, ltErr := localtunnel.Listen(localtunnel.Options{})
114+
if ltErr != nil {
115+
t.Fatal("Error with Localtunnel: ", ltErr)
116+
}
117+
//Create Validation Server & Listen
118+
channel := make(chan bool)
119+
server := createValidationServer(channel)
120+
go server.Serve(listener)
121+
122+
//Extra time for server to set up
123+
time.Sleep(1 * time.Second)
124+
125+
//Create Studio Flow
126+
params := createStudioFlowParams(listener.URL(), method)
127+
resp, flowErr := testClient.StudioV2.CreateFlow(params)
128+
if flowErr != nil {
129+
t.Fatal("Error with Studio Flow Creation: ", flowErr)
130+
}
131+
flowSid := *resp.Sid
132+
executeFlow(t, flowSid)
133+
134+
//Await for Request Validation
135+
afterCh := time.After(5 * time.Second)
136+
select {
137+
case validate := <-channel:
138+
assert.True(t, validate)
139+
case <-afterCh:
140+
t.Fatal("No request was sent to validation server")
141+
}
142+
defer testClient.StudioV2.DeleteFlow(flowSid)
143+
defer server.Close()
144+
}
145+
146+
func TestRequestValidation_GETMethod(t *testing.T) {
147+
requestValidation(t, "GET")
148+
}
149+
150+
func TestRequestValidation_POSTMethod(t *testing.T) {
151+
requestValidation(t, "POST")
152+
}

0 commit comments

Comments
 (0)