Skip to content
This repository was archived by the owner on Sep 26, 2023. It is now read-only.

Commit 2389cce

Browse files
albertpastrananathankleyn
authored andcommitted
Adds tests for ActionConfig.create()
During the process, I've moved the validation from `Config` to each specific action. Currently this only affects year and ranges, whose create can return an error. Because of this, the anonymisations function can now fail and returns an error in case there name can't be match or if the specific action can't be created. Signed-off-by: Albert Pastrana <albert.pastrana@gmail.com>
1 parent 95bd9ef commit 2389cce

File tree

7 files changed

+194
-158
lines changed

7 files changed

+194
-158
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ In order to be useful, Anon needs to be told what you want to do to each column
6565
},
6666
{
6767
// Hash (SHA1) the input.
68-
"name": "hash"
68+
"name": "hash",
69+
// Optional salt that will be appened to the input.
70+
// If not defined, a random salt will be generated
71+
"salt": "salt"
6972
},
7073
{
7174
// Given a date, just keep the year.

anonymisations.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,15 @@ type ActionConfig struct {
3737
}
3838

3939
// Returns an array of anonymisations according to the config
40-
func anonymisations(configs *[]ActionConfig) []Anonymisation {
40+
func anonymisations(configs *[]ActionConfig) ([]Anonymisation, error) {
41+
var err error
4142
res := make([]Anonymisation, len(*configs))
4243
for i, config := range *configs {
43-
res[i] = config.create()
44+
if res[i], err = config.create(); err != nil {
45+
return nil, err
46+
}
4447
}
45-
return res
48+
return res, nil
4649
}
4750

4851
// Returns the configured salt or a random one
@@ -54,20 +57,20 @@ func (ac *ActionConfig) saltOrRandom() string {
5457
return strconv.Itoa(rand.Int())
5558
}
5659

57-
func (ac *ActionConfig) create() Anonymisation {
60+
func (ac *ActionConfig) create() (Anonymisation, error) {
5861
switch ac.Name {
5962
case "nothing":
60-
return identity
63+
return identity, nil
6164
case "outcode":
62-
return outcode
65+
return outcode, nil
6366
case "hash":
64-
return hash(ac.saltOrRandom())
67+
return hash(ac.saltOrRandom()), nil
6568
case "year":
6669
return year(ac.DateConfig.Format)
6770
case "ranges":
6871
return ranges(ac.RangeConfig)
6972
}
70-
return identity
73+
return nil, fmt.Errorf("can't create an action with name %s", ac.Name)
7174
}
7275

7376
// The no-op, returns the input unchanged.
@@ -97,20 +100,32 @@ func outcode(s string) (string, error) {
97100
// If either the format is invalid or the year doesn't
98101
// match that format, it will return an error and
99102
// the input unchanged
100-
func year(format string) Anonymisation {
103+
func year(format string) (Anonymisation, error) {
104+
if _, err := time.Parse(format, format); err != nil {
105+
return nil, err
106+
}
101107
return func(s string) (string, error) {
102108
t, err := time.Parse(format, s)
103109
if err != nil {
104110
return s, err
105111
}
106112
return strconv.Itoa(t.Year()), nil
107-
}
113+
}, nil
108114
}
109115

110116
// Given a list of ranges, it will summarise numeric
111117
// values into groups of values, each group defined
112118
// by a range and an output
113-
func ranges(ranges []RangeConfig) Anonymisation {
119+
func ranges(ranges []RangeConfig) (Anonymisation, error) {
120+
for _, rc := range ranges {
121+
if rc.Gt != nil && rc.Gte != nil || rc.Lt != nil && rc.Lte != nil {
122+
return nil, errors.New("you can only specify one of (gt, gte) and (lt, lte)")
123+
} else if rc.Gt == nil && rc.Gte == nil && rc.Lt == nil && rc.Lte == nil {
124+
return nil, errors.New("you need to specify at least one of gt, gte, lt, lte")
125+
} else if rc.Output == nil {
126+
return nil, errors.New("you need to specify the output for a range")
127+
}
128+
}
114129
return func(s string) (string, error) {
115130
v, err := strconv.ParseFloat(s, 64)
116131
if err != nil {
@@ -122,7 +137,7 @@ func ranges(ranges []RangeConfig) Anonymisation {
122137
}
123138
}
124139
return s, errors.New("No range defined for value")
125-
}
140+
}, nil
126141
}
127142

128143
func (r *RangeConfig) contains(v float64) bool {

anonymisations_test.go

Lines changed: 155 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,167 @@ import (
88
"github.com/leanovate/gopter/gen"
99
"github.com/leanovate/gopter/prop"
1010
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
1112
)
1213

13-
func TestAnonymisations(t *testing.T) {
14-
salt := "jump"
15-
conf := &[]ActionConfig{
16-
ActionConfig{
17-
Name: "nothing",
18-
},
19-
ActionConfig{
20-
Name: "hash",
21-
Salt: &salt,
22-
},
23-
}
24-
// can't test that the functions are equal because of https://github.com/stretchr/testify/issues/182
25-
// and https://github.com/stretchr/testify/issues/159#issuecomment-99557398
26-
// will have to test that the functions return the same
27-
anons := anonymisations(conf)
28-
expectedRes, expectedErr := identity("a")
29-
actualRes, actualErr := anons[0]("a")
30-
assert.Equal(t, expectedRes, actualRes)
31-
assert.Equal(t, expectedErr, actualErr)
32-
expectedRes, expectedErr = hash("jump")("a")
33-
actualRes, actualErr = anons[1]("a")
14+
var salt = "jump"
15+
16+
const seed = int64(1)
17+
18+
//this is the first random salt with the seed above
19+
const firstSalt = "5577006791947779410"
20+
21+
// can't test that the functions are equal because of https://github.com/stretchr/testify/issues/182
22+
// and https://github.com/stretchr/testify/issues/159#issuecomment-99557398
23+
// will have to test that the functions return the same
24+
func assertAnonymisationFunction(t *testing.T, expected Anonymisation, actual Anonymisation, value string) {
25+
require.NotNil(t, expected)
26+
require.NotNil(t, actual)
27+
expectedRes, expectedErr := expected(value)
28+
actualRes, actualErr := actual(value)
3429
assert.Equal(t, expectedRes, actualRes)
3530
assert.Equal(t, expectedErr, actualErr)
3631
}
3732

38-
func TestActionConfig(t *testing.T) {
39-
t.Run("saltOrRandom", func(t *testing.T) {
40-
t.Run("if salt is not specified", func(t *testing.T) {
33+
func TestAnonymisations(t *testing.T) {
34+
t.Run("a valid configuration", func(t *testing.T) {
35+
conf := &[]ActionConfig{
36+
ActionConfig{
37+
Name: "nothing",
38+
},
39+
ActionConfig{
40+
Name: "hash",
41+
Salt: &salt,
42+
},
43+
}
44+
anons, err := anonymisations(conf)
45+
assert.NoError(t, err)
46+
assertAnonymisationFunction(t, identity, anons[0], "a")
47+
assertAnonymisationFunction(t, hash(salt), anons[1], "a")
48+
})
49+
t.Run("an invalid configuration", func(t *testing.T) {
50+
conf := &[]ActionConfig{ActionConfig{Name: "year", DateConfig: DateConfig{Format: "3333"}}}
51+
anons, err := anonymisations(conf)
52+
assert.Error(t, err, "should return an error")
53+
assert.Nil(t, anons)
54+
})
55+
}
56+
57+
func TestActionConfigSaltOrRandom(t *testing.T) {
58+
t.Run("if salt is not specified", func(t *testing.T) {
59+
rand.Seed(seed)
60+
acNoSalt := ActionConfig{Name: "hash"}
61+
assert.Equal(t, firstSalt, acNoSalt.saltOrRandom(), "should return a random salt")
62+
})
63+
t.Run("if salt is specified", func(t *testing.T) {
64+
emptySalt := ""
65+
acEmptySalt := ActionConfig{Name: "hash", Salt: &emptySalt}
66+
assert.Empty(t, acEmptySalt.saltOrRandom(), "should return the empty salt if empty")
67+
68+
acSalt := ActionConfig{Name: "hash", Salt: &salt}
69+
assert.Equal(t, "jump", acSalt.saltOrRandom(), "should return the salt")
70+
})
71+
}
72+
73+
func TestActionConfigCreate(t *testing.T) {
74+
t.Run("invalid name", func(t *testing.T) {
75+
ac := ActionConfig{Name: "invalid name"}
76+
res, err := ac.create()
77+
assert.Error(t, err)
78+
assert.Nil(t, res)
79+
})
80+
t.Run("identity", func(t *testing.T) {
81+
ac := ActionConfig{Name: "nothing"}
82+
res, err := ac.create()
83+
assert.NoError(t, err)
84+
assertAnonymisationFunction(t, identity, res, "a")
85+
})
86+
t.Run("outcode", func(t *testing.T) {
87+
ac := ActionConfig{Name: "outcode"}
88+
res, err := ac.create()
89+
assert.NoError(t, err)
90+
assertAnonymisationFunction(t, outcode, res, "a")
91+
})
92+
t.Run("hash", func(t *testing.T) {
93+
t.Run("if salt is not specified uses a random salt", func(t *testing.T) {
4194
rand.Seed(1)
42-
acNoSalt := ActionConfig{Name: "hash"}
43-
assert.Equal(t, "5577006791947779410", acNoSalt.saltOrRandom(), "should return a random salt")
95+
ac := ActionConfig{Name: "hash"}
96+
res, err := ac.create()
97+
assert.NoError(t, err)
98+
assertAnonymisationFunction(t, hash(firstSalt), res, "a")
99+
})
100+
t.Run("if salt is specified uses it", func(t *testing.T) {
101+
ac := ActionConfig{Name: "hash", Salt: &salt}
102+
res, err := ac.create()
103+
assert.NoError(t, err)
104+
assertAnonymisationFunction(t, hash(salt), res, "a")
105+
})
106+
})
107+
t.Run("year", func(t *testing.T) {
108+
t.Run("with an invalid format", func(t *testing.T) {
109+
ac := ActionConfig{Name: "year", DateConfig: DateConfig{Format: "11112233"}}
110+
res, err := ac.create()
111+
assert.Error(t, err, "should fail")
112+
assert.Nil(t, res)
113+
})
114+
t.Run("with a valid format", func(t *testing.T) {
115+
ac := ActionConfig{Name: "year", DateConfig: DateConfig{Format: "20060102"}}
116+
res, err := ac.create()
117+
assert.NoError(t, err, "should not fail")
118+
y, err := year("20060102")
119+
assert.NoError(t, err)
120+
assertAnonymisationFunction(t, y, res, "21121212")
121+
})
122+
})
123+
t.Run("ranges", func(t *testing.T) {
124+
num := 2.0
125+
output := "0-100"
126+
t.Run("range has at least one of lt, lte, gt, gte", func(t *testing.T) {
127+
ac := ActionConfig{
128+
Name: "ranges",
129+
RangeConfig: []RangeConfig{RangeConfig{Output: &output}},
130+
}
131+
r, err := ac.create()
132+
assert.Error(t, err, "if not should return an error")
133+
assert.Nil(t, r)
134+
})
135+
t.Run("range contains both lt and lte", func(t *testing.T) {
136+
ac := ActionConfig{
137+
Name: "ranges",
138+
RangeConfig: []RangeConfig{RangeConfig{Lt: &num, Lte: &num, Output: &output}},
139+
}
140+
r, err := ac.create()
141+
assert.Error(t, err, "if not should return an error")
142+
assert.Nil(t, r)
143+
})
144+
t.Run("range contains both gt and gte", func(t *testing.T) {
145+
ac := ActionConfig{
146+
Name: "ranges",
147+
RangeConfig: []RangeConfig{RangeConfig{Gt: &num, Gte: &num, Output: &output}},
148+
}
149+
r, err := ac.create()
150+
assert.Error(t, err, "if not should return an error")
151+
assert.Nil(t, r)
152+
})
153+
t.Run("range without output defined", func(t *testing.T) {
154+
ac := ActionConfig{
155+
Name: "ranges",
156+
RangeConfig: []RangeConfig{RangeConfig{Lt: &num, Gte: &num}},
157+
}
158+
r, err := ac.create()
159+
assert.Error(t, err, "if not should return an error")
160+
assert.Nil(t, r)
44161
})
45-
t.Run("if salt is specified", func(t *testing.T) {
46-
emptySalt := ""
47-
acEmptySalt := ActionConfig{Name: "hash", Salt: &emptySalt}
48-
assert.Empty(t, acEmptySalt.saltOrRandom(), "should return the empty salt if empty")
49-
50-
salt := "jump"
51-
acSalt := ActionConfig{Name: "hash", Salt: &salt}
52-
assert.Equal(t, "jump", acSalt.saltOrRandom(), "should return the salt")
162+
t.Run("valid range", func(t *testing.T) {
163+
rangeConfigs := []RangeConfig{RangeConfig{Lte: &num, Gte: &num, Output: &output}}
164+
ac := ActionConfig{
165+
Name: "ranges",
166+
RangeConfig: rangeConfigs,
167+
}
168+
r, err := ac.create()
169+
expected, _ := ranges(rangeConfigs)
170+
assert.NoError(t, err)
171+
assertAnonymisationFunction(t, expected, r, "2")
53172
})
54173
})
55174
}
@@ -108,7 +227,7 @@ func TestOutcode(t *testing.T) {
108227
}
109228

110229
func TestYear(t *testing.T) {
111-
f := year("20060102")
230+
f, _ := year("20060102")
112231
t.Run("if the date can be parsed", func(t *testing.T) {
113232
res, err := f("20120102")
114233
assert.NoError(t, err, "should return no error")
@@ -124,7 +243,7 @@ func TestRanges(t *testing.T) {
124243
min := 0.0
125244
max := 100.0
126245
output := "0-100"
127-
f := ranges([]RangeConfig{RangeConfig{Gt: &min, Lte: &max, Output: &output}})
246+
f, _ := ranges([]RangeConfig{RangeConfig{Gt: &min, Lte: &max, Output: &output}})
128247
t.Run("if the value is not a float", func(t *testing.T) {
129248
res, err := f("input")
130249
assert.Error(t, err, "should return an error")

config.go

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

33
import (
44
"encoding/json"
5-
"errors"
65
"os"
76
)
87

@@ -53,18 +52,3 @@ func loadConfig(filename string) (*Config, error) {
5352
}
5453
return &conf, err
5554
}
56-
57-
func (conf *Config) validate() error {
58-
for _, action := range conf.Actions {
59-
for _, rc := range action.RangeConfig {
60-
if rc.Gt != nil && rc.Gte != nil || rc.Lt != nil && rc.Lte != nil {
61-
return errors.New("You can only specify one of (gt, gte) and (lt, lte)")
62-
} else if rc.Gt == nil && rc.Gte == nil && rc.Lt == nil && rc.Lte == nil {
63-
return errors.New("You need to specify at least one of gt, gte, lt, lte")
64-
} else if rc.Output == nil {
65-
return errors.New("You need to specify the output for a range")
66-
}
67-
}
68-
}
69-
return nil
70-
}

0 commit comments

Comments
 (0)