diff --git a/core/templating/template_helpers.go b/core/templating/template_helpers.go index 26b5149f6..36d8706bf 100644 --- a/core/templating/template_helpers.go +++ b/core/templating/template_helpers.go @@ -212,7 +212,7 @@ func (t templateHelpers) faker(fakerType string) []reflect.Value { return []reflect.Value{} } -func (t templateHelpers) parseCsv(dataSourceName, searchFieldName, searchFieldValue, returnFieldName string, options *raymond.Options) string { +func (t templateHelpers) fetchSingleFieldCsv(dataSourceName, searchFieldName, searchFieldValue, returnFieldName string, options *raymond.Options) string { templateDataSources := t.TemplateDataSource.DataSources source, exists := templateDataSources[dataSourceName] @@ -248,6 +248,81 @@ func (t templateHelpers) parseCsv(dataSourceName, searchFieldName, searchFieldVa } +func (t templateHelpers) fetchMatchingRowsCsv(dataSourceName string, searchFieldName string, searchFieldValue string) []map[string]string { + templateDataSources := t.TemplateDataSource.DataSources + source, exists := templateDataSources[dataSourceName] + if !exists { + log.Debug("could not find datasource " + dataSourceName) + return []map[string]string{} + } + if len(source.Data) < 1 { + log.Debug("no data available in datasource " + dataSourceName) + return []map[string]string{} + } + headers := source.Data[0] + fieldIndex := -1 + for i, header := range headers { + if header == searchFieldName { + fieldIndex = i + break + } + } + if fieldIndex == -1 { + log.Debug("could not find search field name " + searchFieldName) + return []map[string]string{} + } + var result []map[string]string + for _, row := range source.Data[1:] { + if fieldIndex < len(row) && row[fieldIndex] == searchFieldValue { + rowMap := make(map[string]string) + for i, cell := range row { + if i < len(headers) { + rowMap[headers[i]] = cell + } + } + result = append(result, rowMap) + } + } + + return result +} + +func (t templateHelpers) csvAsArray(dataSourceName string) [][]string { + templateDataSources := t.TemplateDataSource.DataSources + source, exists := templateDataSources[dataSourceName] + if exists { + return source.Data + } else { + log.Debug("could not find datasource " + dataSourceName) + return [][]string{} + } +} + +func (t templateHelpers) csvAsMap(dataSourceName string) []map[string]string { + templateDataSources := t.TemplateDataSource.DataSources + source, exists := templateDataSources[dataSourceName] + if !exists { + log.Debug("could not find datasource " + dataSourceName) + return []map[string]string{} + } + if len(source.Data) < 1 { + log.Debug("no data available in datasource " + dataSourceName) + return []map[string]string{} + } + headers := source.Data[0] + var result []map[string]string + for _, row := range source.Data[1:] { + rowMap := make(map[string]string) + for i, cell := range row { + if i < len(headers) { + rowMap[headers[i]] = cell + } + } + result = append(result, rowMap) + } + return result +} + func (t templateHelpers) parseJournalBasedOnIndex(indexName, keyValue, dataSource, queryType, lookupQuery string, options *raymond.Options) interface{} { journalDetails := options.Value("Journal").(Journal) if journalEntry, err := getIndexEntry(journalDetails, indexName, keyValue); err == nil { diff --git a/core/templating/templating.go b/core/templating/templating.go index 1016328a8..6a25ee4be 100644 --- a/core/templating/templating.go +++ b/core/templating/templating.go @@ -100,7 +100,10 @@ func NewTemplator() *Templator { helperMethodMap["matchesRegex"] = t.matchesRegex helperMethodMap["faker"] = t.faker helperMethodMap["requestBody"] = t.requestBody - helperMethodMap["csv"] = t.parseCsv + helperMethodMap["csv"] = t.fetchSingleFieldCsv + helperMethodMap["csvMatchingRows"] = t.fetchMatchingRowsCsv + helperMethodMap["csvAsArray"] = t.csvAsArray + helperMethodMap["csvAsMap"] = t.csvAsMap helperMethodMap["journal"] = t.parseJournalBasedOnIndex helperMethodMap["hasJournalKey"] = t.hasJournalKey helperMethodMap["setStatusCode"] = t.setStatusCode diff --git a/core/templating/templating_test.go b/core/templating/templating_test.go index ca419deb7..0e15567dc 100644 --- a/core/templating/templating_test.go +++ b/core/templating/templating_test.go @@ -1,9 +1,10 @@ package templating_test import ( - "github.com/SpectoLabs/hoverfly/core/journal" "testing" + "github.com/SpectoLabs/hoverfly/core/journal" + "github.com/SpectoLabs/hoverfly/core/models" "github.com/SpectoLabs/hoverfly/core/templating" . "github.com/onsi/gomega" @@ -71,6 +72,74 @@ func Test_ApplyTemplate_ParseCsv_WithEachBlockAndMissingDataSource(t *testing.T) Expect(template).To(Equal(` 0 : Product Name with productId 1 is {{ csv products productId 1 productName }} 1 : Product Name with productId 2 is {{ csv products productId 2 productName }} `)) } +// -------------------------------------- +func Test_ApplyTemplate_MatchingRowsCsvAndReturnMatchedString(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{#each (csvMatchingRows 'test-csv1' 'id' '2')}}{{this.name}}{{/each}}`) + + Expect(err).To(BeNil()) + Expect(template).To(Equal(`Test2`)) +} + +func Test_ApplyTemplate_MatchingRowsCsvMissingDataSource(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{#each (csvMatchingRows 'test-csv99' 'id' '2')}}{{this.name}}{{/each}}`) + + Expect(err).To(BeNil()) + Expect(template).To(Equal(``)) +} + +func Test_ApplyTemplate_MatchingRowsCsvInvalidKey(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{#each (csvMatchingRows 'test-csv1' 'id' '99')}}{{this.name}}{{/each}}`) + + Expect(err).To(BeNil()) + Expect(template).To(Equal(``)) +} + +// ------------------------------- +func Test_ApplyTemplate_CsvAsArrayAndReturnMatchedString(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{#each (csvAsArray 'test-csv1')}}{{#each this}}{{this}}{{/each}}{{/each}}`) + + Expect(err).To(BeNil()) + Expect(template).To(Equal(`idnamemarks1Test1552Test256*DummyABSENT`)) +} + +func Test_ApplyTemplate_CsvAsArrayMissingDataSource(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{#each (csvAsArray 'test-csv99')}}{{#each this}}{{this}}{{/each}}{{/each}}`) + + Expect(err).To(BeNil()) + Expect(template).To(Equal(``)) +} + +// ------------------------------- +func Test_ApplyTemplate_CsvAsMapAndReturnMatchedString(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{#each (csvAsMap 'test-csv1')}}{{this.name}}{{/each}}`) + + Expect(err).To(BeNil()) + Expect(template).To(Equal(`Test1Test2Dummy`)) +} + +func Test_ApplyTemplate_CsvAsMapMissingDataSource(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{#each (csvAsMap 'test-csv99')}}{{this.name}}{{/each}}`) + + Expect(err).To(BeNil()) + Expect(template).To(Equal(``)) +} + +// ------------------------------- + func Test_ApplyTemplate_EachBlockWithSplitTemplatingFunction(t *testing.T) { RegisterTestingT(t) @@ -642,7 +711,7 @@ func Test_ApplyTemplate_Arithmetic_Ops_With_Each_Block(t *testing.T) { Expect(result).To(Equal(` 3.5 9 total: 12.50`)) // Running the second time should produce the same result because each execution has its own context data. - result, err = templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{}) + result, err = templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{}) Expect(err).To(BeNil()) Expect(result).To(Equal(` 3.5 9 total: 12.50`)) }