Skip to content

Commit 93d8a29

Browse files
committed
Add parser, supporting files and tests
1 parent 3b27833 commit 93d8a29

File tree

9 files changed

+761
-0
lines changed

9 files changed

+761
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
running
2+
processed/

call-event-file.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
"time"
8+
)
9+
10+
const (
11+
DATE_TIME_FORMAT = "2006-02-01 15:04:05"
12+
COL_EVENT_DATETIME = 0
13+
COL_EVENT_ACTION = 1
14+
COL_CALL_REF = 2
15+
COL_EVENT_VAL = 3
16+
COL_EVENT_CURRENCY_CODE = 4
17+
)
18+
19+
type CallEventFile struct {
20+
filenamePath string
21+
validData [][]string // Record
22+
recordErrors []string // LogMessage
23+
numberOfRecords int
24+
}
25+
26+
func (f *CallEventFile) GetFilename() string {
27+
parts := strings.Split(f.filenamePath, "/")
28+
29+
return parts[len(parts)-1:][0]
30+
}
31+
32+
func (f *CallEventFile) RecordErrors() []string {
33+
return f.recordErrors
34+
}
35+
36+
func (f *CallEventFile) ValidData() [][]string {
37+
return f.validData
38+
}
39+
40+
func CreateCallEventFileFromRaw(rawData [][]string, filepath string) CallEventFile {
41+
data := removeHeader(rawData)
42+
validRecords := [][]string{}
43+
errorRecords := []string{}
44+
45+
for i, row := range data {
46+
// COL_EVENT_DATETIME
47+
eventDateTimeValue := row[COL_EVENT_DATETIME]
48+
49+
if eventDateTimeValue == "" {
50+
errorRecords = append(
51+
errorRecords,
52+
fmt.Sprintf("File: %#v. Record: %#v requires 'eventDatetime' value", filepath, i+1),
53+
)
54+
continue
55+
}
56+
57+
_, err := time.Parse(DATE_TIME_FORMAT, eventDateTimeValue)
58+
if err != nil {
59+
errorRecords = append(
60+
errorRecords,
61+
fmt.Sprintf("File: %#v. Record: %#v date format must be yyyy-mm-dd hh:mm:ss", filepath, i+1),
62+
)
63+
continue
64+
}
65+
66+
// COL_EVENT_ACTION
67+
if row[COL_EVENT_ACTION] == "" {
68+
errorRecords = append(
69+
errorRecords,
70+
fmt.Sprintf("File: %#v. Record: %#v requires 'eventAction' value", filepath, i+1),
71+
)
72+
continue
73+
}
74+
75+
if l := len(row[COL_EVENT_ACTION]); l == 0 || l > 20 {
76+
errorRecords = append(
77+
errorRecords,
78+
fmt.Sprintf("File: %#v. Record: %#v requires 'eventAction' to be between 1 - 20 in length", filepath, i+1),
79+
)
80+
continue
81+
}
82+
83+
// COL_CALL_REF
84+
if row[COL_CALL_REF] == "" {
85+
errorRecords = append(
86+
errorRecords,
87+
fmt.Sprintf("File: %#v. Record: %#v requires 'callRef' value", filepath, i+1),
88+
)
89+
continue
90+
}
91+
92+
if _, err := strconv.ParseInt(row[COL_CALL_REF], 10, 64); err != nil {
93+
errorRecords = append(
94+
errorRecords,
95+
fmt.Sprintf("File: %#v. Record: %#v requires 'callRef' to be a valid integer", filepath, i+1),
96+
)
97+
continue
98+
}
99+
100+
// COL_EVENT_VAL
101+
if row[COL_EVENT_VAL] == "" {
102+
row[COL_EVENT_VAL] = "0.00"
103+
}
104+
105+
eventValue, err := strconv.ParseFloat(row[COL_EVENT_VAL], 64)
106+
107+
if err != nil {
108+
errorRecords = append(
109+
errorRecords,
110+
fmt.Sprintf("File: %#v. Record: %#v requires 'eventValue' to be a valid float", filepath, i+1),
111+
)
112+
continue
113+
}
114+
115+
// COL_EVENT_CURRENCY_CODE
116+
if row[COL_EVENT_CURRENCY_CODE] == "" && eventValue > 0.0 {
117+
errorRecords = append(
118+
errorRecords,
119+
fmt.Sprintf("File: %#v. Record: %#v requires 'eventCurrencyCode' if event value is more than 0.0", filepath, i+1),
120+
)
121+
continue
122+
}
123+
124+
validRecords = append(validRecords, row)
125+
}
126+
127+
return CallEventFile{filepath, validRecords, errorRecords, len(data)}
128+
}
129+
130+
func removeHeader(rawCsv [][]string) [][]string {
131+
// untested
132+
if len(rawCsv) > 0 {
133+
return rawCsv[1:]
134+
}
135+
136+
return rawCsv
137+
}

call-event-file_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package main_test
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
parser "github.com/kaschula/call-event-parser"
8+
. "github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestItReturnsAnErrorWhenNoEventDateTimeGiven(t *testing.T) {
12+
rawCsv := [][]string{
13+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
14+
[]string{""},
15+
}
16+
17+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
18+
19+
Equal(t, 0, len(callFile.ValidData()))
20+
True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventDatetime' value"))
21+
}
22+
23+
func TestItReturnsAnErrorWhenDateFormatIsIncorrect(t *testing.T) {
24+
rawCsv := [][]string{
25+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
26+
[]string{"2012-01-02 12-01-30"},
27+
}
28+
29+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
30+
31+
Equal(t, 0, len(callFile.ValidData()))
32+
True(t, strings.Contains(callFile.RecordErrors()[0], "date format must be yyyy-mm-dd hh:mm:ss"))
33+
}
34+
35+
func TestItReturnsAnErrorWhenEventActionIsMissing(t *testing.T) {
36+
rawCsv := [][]string{
37+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
38+
[]string{"2012-01-02 12:01:30", ""},
39+
}
40+
41+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
42+
43+
Equal(t, 0, len(callFile.ValidData()))
44+
True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventAction' value"))
45+
}
46+
47+
func TestItReturnsAnErrorWhenEventActionIsMoreThan20CharactersLong(t *testing.T) {
48+
rawCsv := [][]string{
49+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
50+
[]string{"2012-01-02 12:01:30", "abcdefghijklmnopqrstu"},
51+
}
52+
53+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
54+
55+
Equal(t, 0, len(callFile.ValidData()))
56+
True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventAction' to be between 1 - 20 in length"))
57+
}
58+
59+
func TestItReturnsAnErrorWhenCallRefIsMissing(t *testing.T) {
60+
rawCsv := [][]string{
61+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
62+
[]string{"2012-01-02 12:01:30", "sale", ""},
63+
}
64+
65+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
66+
67+
Equal(t, 0, len(callFile.ValidData()))
68+
True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'callRef' value"))
69+
}
70+
71+
func TestItReturnsAnErrorWhenCallRefIsNotAValidInteger(t *testing.T) {
72+
rawCsv := [][]string{
73+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
74+
[]string{"2012-01-02 12:01:30", "sale", "notAnInt"},
75+
}
76+
77+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
78+
79+
Equal(t, 0, len(callFile.ValidData()))
80+
True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'callRef' to be a valid integer"))
81+
}
82+
83+
func TestItReturnsAnErrorWhenEventValueIsNoteAValidFloat(t *testing.T) {
84+
rawCsv := [][]string{
85+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
86+
[]string{"2012-01-02 12:01:30", "sale", "1234", "2.1a"},
87+
}
88+
89+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
90+
91+
Equal(t, 0, len(callFile.ValidData()))
92+
True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventValue' to be a valid float"))
93+
}
94+
95+
func TestItReturnsAnErrorWhenEventCurrencyCodeIsNotSetAndEventValueIsMoreThanZero(t *testing.T) {
96+
rawCsv := [][]string{
97+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
98+
[]string{"2012-01-02 12:01:30", "sale", "1234", "1.0", ""},
99+
}
100+
101+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
102+
103+
Equal(t, 0, len(callFile.ValidData()))
104+
True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventCurrencyCode' if event value is more than 0.0"))
105+
}
106+
107+
func TestItDoesNotReturnAnErrorWhenEventValueIsZeroAndNoCurrencyCodeSet(t *testing.T) {
108+
rawCsv := [][]string{
109+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
110+
[]string{"2012-01-02 12:01:30", "sale", "1234", "0.0", ""},
111+
}
112+
113+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
114+
115+
Equal(t, 0, len(callFile.RecordErrors()))
116+
Equal(t, 1, len(callFile.ValidData()))
117+
}
118+
119+
func TestItReturnsItSetsTheEventValueToZeroFloatIfEmpty(t *testing.T) {
120+
rawCsv := [][]string{
121+
[]string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"},
122+
[]string{"2012-01-02 12:01:30", "sale", "1234", "", "GBP"},
123+
}
124+
125+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file")
126+
127+
Equal(t, 1, len(callFile.ValidData()))
128+
Equal(t, "0.00", callFile.ValidData()[0][parser.COL_EVENT_VAL])
129+
}
130+
131+
func TestItReturnsTheFileName(t *testing.T) {
132+
rawCsv := [][]string{}
133+
134+
callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file.txt")
135+
136+
Equal(t, "file.txt", callFile.GetFilename())
137+
}

0 commit comments

Comments
 (0)