Skip to content

Commit c81076b

Browse files
committed
Add templating function to dynamically change status code according to response body
1 parent 9570500 commit c81076b

File tree

6 files changed

+97
-107
lines changed

6 files changed

+97
-107
lines changed

core/hoverfly_funcs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func (hf *Hoverfly) applyTransitionsStateTemplating(requestDetails *models.Reque
219219
state := make(map[string]string)
220220

221221
for k, v := range stateTemplates {
222-
state[k], err = hf.templator.RenderTemplate(v, requestDetails, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
222+
state[k], err = hf.templator.RenderTemplate(v, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
223223
if err != nil {
224224
return nil, err
225225
}
@@ -240,7 +240,7 @@ func (hf *Hoverfly) applyBodyTemplating(requestDetails *models.RequestDetails, r
240240
}
241241
}
242242

243-
return hf.templator.RenderTemplate(template, requestDetails, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
243+
return hf.templator.RenderTemplate(template, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
244244
}
245245

246246
func (hf *Hoverfly) applyHeadersTemplating(requestDetails *models.RequestDetails, response *models.ResponseDetails, cachedResponse *models.CachedResponse) (map[string][]string, error) {
@@ -275,7 +275,7 @@ func (hf *Hoverfly) applyHeadersTemplating(requestDetails *models.RequestDetails
275275
for k, v := range headersTemplates {
276276
header = make([]string, len(v))
277277
for i, h := range v {
278-
header[i], err = hf.templator.RenderTemplate(h, requestDetails, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
278+
header[i], err = hf.templator.RenderTemplate(h, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
279279

280280
if err != nil {
281281
return nil, err

core/templating/template_helpers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ func (t templateHelpers) parseJournalBasedOnIndex(indexName, keyValue, dataSourc
180180
return getEvaluationString("journal", options)
181181
}
182182

183+
func (t templateHelpers) setStatusCode(statusCode int, options *raymond.Options) string {
184+
internalVars := options.ValueFromAllCtx("InternalVars").(map[string]interface{})
185+
internalVars["statusCode"] = statusCode
186+
return ""
187+
}
188+
183189
func (t templateHelpers) sum(numbers []string, format string) string {
184190
return sumNumbers(numbers, format)
185191
}

core/templating/templating.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type TemplatingData struct {
2626
Vars map[string]interface{}
2727
Journal Journal
2828
Kvs map[string]interface{}
29+
InternalVars map[string]interface{} // data store used internally by templating helpers
2930
}
3031

3132
type Request struct {
@@ -90,6 +91,7 @@ func NewTemplator() *Templator {
9091
helperMethodMap["requestBody"] = t.requestBody
9192
helperMethodMap["csv"] = t.parseCsv
9293
helperMethodMap["journal"] = t.parseJournalBasedOnIndex
94+
helperMethodMap["setStatusCode"] = t.setStatusCode
9395
helperMethodMap["sum"] = t.sum
9496
helperMethodMap["add"] = t.add
9597
helperMethodMap["subtract"] = t.subtract
@@ -115,20 +117,27 @@ func (*Templator) ParseTemplate(responseBody string) (*raymond.Template, error)
115117
return raymond.Parse(responseBody)
116118
}
117119

118-
func (t *Templator) RenderTemplate(tpl *raymond.Template, requestDetails *models.RequestDetails, literals *models.Literals, vars *models.Variables, state map[string]string, journal *journal.Journal) (string, error) {
120+
func (t *Templator) RenderTemplate(tpl *raymond.Template, requestDetails *models.RequestDetails, response *models.ResponseDetails, literals *models.Literals, vars *models.Variables, state map[string]string, journal *journal.Journal) (string, error) {
119121
if tpl == nil {
120122
return "", fmt.Errorf("template cannot be nil")
121123
}
122124

123-
ctx := t.NewTemplatingData(requestDetails, literals, vars, state, journal)
124-
return tpl.Exec(ctx)
125+
ctx := t.NewTemplatingData(requestDetails, response, literals, vars, state, journal)
126+
result, err := tpl.Exec(ctx)
127+
if err == nil {
128+
statusCode, ok := ctx.InternalVars["statusCode"]
129+
if ok {
130+
response.Status = statusCode.(int)
131+
}
132+
}
133+
return result, err
125134
}
126135

127136
func (templator *Templator) GetSupportedMethodMap() map[string]interface{} {
128137
return templator.SupportedMethodMap
129138
}
130139

131-
func (t *Templator) NewTemplatingData(requestDetails *models.RequestDetails, literals *models.Literals, vars *models.Variables, state map[string]string, journal *journal.Journal) *TemplatingData {
140+
func (t *Templator) NewTemplatingData(requestDetails *models.RequestDetails, response *models.ResponseDetails, literals *models.Literals, vars *models.Variables, state map[string]string, journal *journal.Journal) *TemplatingData {
132141

133142
literalMap := make(map[string]interface{})
134143
if literals != nil {
@@ -176,6 +185,7 @@ func (t *Templator) NewTemplatingData(requestDetails *models.RequestDetails, lit
176185
return a1 + " " + a2 + " " + a3
177186
},
178187
Kvs: kvs,
188+
InternalVars: make(map[string]interface{}),
179189
}
180190

181191
}

core/templating/templating_test.go

Lines changed: 45 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -106,55 +106,37 @@ func Test_ApplyTemplate_EachBlockWithCsvTemplatingFunctionAndLargeInteger(t *tes
106106
func Test_ShouldCreateTemplatingDataPathsFromRequest(t *testing.T) {
107107
RegisterTestingT(t)
108108

109-
actual := templating.NewTemplator().NewTemplatingData(
110-
&models.RequestDetails{
111-
Scheme: "http",
112-
Destination: "test.com",
113-
Path: "/foo/bar",
114-
},
115-
&models.Literals{},
116-
&models.Variables{},
117-
make(map[string]string),
118-
&journal.Journal{},
119-
)
109+
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
110+
Scheme: "http",
111+
Destination: "test.com",
112+
Path: "/foo/bar",
113+
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
120114

121115
Expect(actual.Request.Path).To(ConsistOf("foo", "bar"))
122116
}
123117

124118
func Test_ShouldCreateTemplatingDataPathsFromRequestWithNoPaths(t *testing.T) {
125119
RegisterTestingT(t)
126120

127-
actual := templating.NewTemplator().NewTemplatingData(
128-
&models.RequestDetails{
129-
Scheme: "http",
130-
Destination: "test.com",
131-
},
132-
&models.Literals{},
133-
&models.Variables{},
134-
make(map[string]string),
135-
&journal.Journal{},
136-
)
121+
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
122+
Scheme: "http",
123+
Destination: "test.com",
124+
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
137125

138126
Expect(actual.Request.Path).To(BeEmpty())
139127
}
140128

141129
func Test_ShouldCreateTemplatingDataQueryParamsFromRequest(t *testing.T) {
142130
RegisterTestingT(t)
143131

144-
actual := templating.NewTemplator().NewTemplatingData(
145-
&models.RequestDetails{
146-
Scheme: "http",
147-
Destination: "test.com",
148-
Query: map[string][]string{
149-
"cheese": {"1", "3"},
150-
"ham": {"2"},
151-
},
132+
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
133+
Scheme: "http",
134+
Destination: "test.com",
135+
Query: map[string][]string{
136+
"cheese": {"1", "3"},
137+
"ham": {"2"},
152138
},
153-
&models.Literals{},
154-
&models.Variables{},
155-
make(map[string]string),
156-
&journal.Journal{},
157-
)
139+
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
158140

159141
Expect(actual.Request.QueryParam).To(HaveKeyWithValue("cheese", []string{"1", "3"}))
160142
Expect(actual.Request.QueryParam).To(HaveKeyWithValue("ham", []string{"2"}))
@@ -164,54 +146,36 @@ func Test_ShouldCreateTemplatingDataQueryParamsFromRequest(t *testing.T) {
164146
func Test_ShouldCreateTemplatingDataQueryParamsFromRequestWithNoQueryParams(t *testing.T) {
165147
RegisterTestingT(t)
166148

167-
actual := templating.NewTemplator().NewTemplatingData(
168-
&models.RequestDetails{
169-
Scheme: "http",
170-
Destination: "test.com",
171-
},
172-
&models.Literals{},
173-
&models.Variables{},
174-
make(map[string]string),
175-
&journal.Journal{},
176-
)
149+
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
150+
Scheme: "http",
151+
Destination: "test.com",
152+
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
177153

178154
Expect(actual.Request.QueryParam).To(BeEmpty())
179155
}
180156

181157
func Test_ShouldCreateTemplatingDataHttpScheme(t *testing.T) {
182158
RegisterTestingT(t)
183159

184-
actual := templating.NewTemplator().NewTemplatingData(
185-
&models.RequestDetails{
186-
Scheme: "http",
187-
Destination: "test.com",
188-
},
189-
&models.Literals{},
190-
&models.Variables{},
191-
make(map[string]string),
192-
&journal.Journal{},
193-
)
160+
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
161+
Scheme: "http",
162+
Destination: "test.com",
163+
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
194164

195165
Expect(actual.Request.Scheme).To(Equal("http"))
196166
}
197167

198168
func Test_ShouldCreateTemplatingDataHeaderFromRequest(t *testing.T) {
199169
RegisterTestingT(t)
200170

201-
actual := templating.NewTemplator().NewTemplatingData(
202-
&models.RequestDetails{
203-
Scheme: "http",
204-
Destination: "test.com",
205-
Headers: map[string][]string{
206-
"cheese": {"1", "3"},
207-
"ham": {"2"},
208-
},
171+
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
172+
Scheme: "http",
173+
Destination: "test.com",
174+
Headers: map[string][]string{
175+
"cheese": {"1", "3"},
176+
"ham": {"2"},
209177
},
210-
&models.Literals{},
211-
&models.Variables{},
212-
make(map[string]string),
213-
&journal.Journal{},
214-
)
178+
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
215179

216180
Expect(actual.Request.Header).To(HaveKeyWithValue("cheese", []string{"1", "3"}))
217181
Expect(actual.Request.Header).To(HaveKeyWithValue("ham", []string{"2"}))
@@ -221,16 +185,10 @@ func Test_ShouldCreateTemplatingDataHeaderFromRequest(t *testing.T) {
221185
func Test_ShouldCreateTemplatingDataHeaderFromRequestWithNoHeader(t *testing.T) {
222186
RegisterTestingT(t)
223187

224-
actual := templating.NewTemplator().NewTemplatingData(
225-
&models.RequestDetails{
226-
Scheme: "http",
227-
Destination: "test.com",
228-
},
229-
&models.Literals{},
230-
&models.Variables{},
231-
make(map[string]string),
232-
&journal.Journal{},
233-
)
188+
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
189+
Scheme: "http",
190+
Destination: "test.com",
191+
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
234192

235193
Expect(actual.Request.Header).To(BeEmpty())
236194
}
@@ -554,16 +512,10 @@ func Test_VarSetToNilInCaseOfInvalidArgsPassed(t *testing.T) {
554512
},
555513
}
556514

557-
actual := templator.NewTemplatingData(
558-
&models.RequestDetails{
559-
Scheme: "http",
560-
Destination: "test.com",
561-
},
562-
&models.Literals{},
563-
vars,
564-
make(map[string]string),
565-
&journal.Journal{},
566-
)
515+
actual := templator.NewTemplatingData(&models.RequestDetails{
516+
Scheme: "http",
517+
Destination: "test.com",
518+
}, nil, &models.Literals{}, vars, make(map[string]string), &journal.Journal{})
567519

568520
Expect(actual.Vars["varOne"]).To(BeNil())
569521

@@ -581,15 +533,9 @@ func Test_VarSetToProperValueInCaseOfRequestDetailsPassedAsArgument(t *testing.T
581533
},
582534
}
583535

584-
actual := templator.NewTemplatingData(
585-
&models.RequestDetails{
586-
Path: "/part1/foo,bar",
587-
},
588-
&models.Literals{},
589-
vars,
590-
make(map[string]string),
591-
&journal.Journal{},
592-
)
536+
actual := templator.NewTemplatingData(&models.RequestDetails{
537+
Path: "/part1/foo,bar",
538+
}, nil, &models.Literals{}, vars, make(map[string]string), &journal.Journal{})
593539

594540
Expect(actual.Vars["splitRequestPath"]).ToNot(BeNil())
595541
Expect(len(actual.Vars["splitRequestPath"].([]string))).To(Equal(2))
@@ -690,13 +636,13 @@ func Test_ApplyTemplate_Arithmetic_Ops_With_Each_Block(t *testing.T) {
690636

691637
template, _ := templator.ParseTemplate(responseBody)
692638
state := make(map[string]string)
693-
result, err := templator.RenderTemplate(template, requestDetails, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
639+
result, err := templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
694640

695641
Expect(err).To(BeNil())
696642
Expect(result).To(Equal(` 3.5 9 total: 12.50`))
697643

698644
// Running the second time should produce the same result because each execution has its own context data.
699-
result, err = templator.RenderTemplate(template, requestDetails, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
645+
result, err = templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
700646
Expect(err).To(BeNil())
701647
Expect(result).To(Equal(` 3.5 9 total: 12.50`))
702648
}
@@ -743,5 +689,5 @@ func ApplyTemplate(requestDetails *models.RequestDetails, state map[string]strin
743689

744690
template, err := templator.ParseTemplate(responseBody)
745691
Expect(err).To(BeNil())
746-
return templator.RenderTemplate(template, requestDetails, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
692+
return templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
747693
}

functional-tests/core/ft_templated_response_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,19 @@ var _ = Describe("When I run Hoverfly", func() {
493493
Expect(string(body)).To(Equal("test-server.com"))
494494
})
495495

496-
It("Gloabl literals and variables", func() {
496+
It("SetStatusCode", func() {
497+
hoverfly.ImportSimulation(testdata.TemplatingRequest)
498+
499+
resp := hoverfly.Proxy(sling.New().Get("http://test-server.com/SetStatusCode"))
500+
Expect(resp.StatusCode).To(Equal(202))
501+
502+
body, err := io.ReadAll(resp.Body)
503+
Expect(err).To(BeNil())
504+
505+
Expect(string(body)).To(Equal(""))
506+
})
507+
508+
It("Global literals and variables", func() {
497509
hoverfly.ImportSimulation(testdata.TemplatingRequest)
498510
resp := hoverfly.Proxy(sling.New().Post("http://test-server.com/global").BodyJSON(map[string]string{
499511
"city": "London",

functional-tests/testdata/templating_request.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,22 @@ var TemplatingRequest = `{
211211
"templated": true
212212
}
213213
},
214+
{
215+
"request": {
216+
"path": [
217+
{
218+
"matcher": "exact",
219+
"value": "/SetStatusCode"
220+
}
221+
]
222+
},
223+
"response": {
224+
"status": 200,
225+
"body": "{{ setStatusCode 202 }}",
226+
"encodedBody": false,
227+
"templated": true
228+
}
229+
},
214230
{
215231
"request": {
216232
"path": [

0 commit comments

Comments
 (0)