diff --git a/.gitignore b/.gitignore index 5a0680d..11ea74e 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ # Logs and databases # ###################### *.log -*.sql *.sqlite # OS generated files # diff --git a/.golangci.json b/.golangci.json new file mode 100644 index 0000000..8747cf5 --- /dev/null +++ b/.golangci.json @@ -0,0 +1,20 @@ +{ + "linters": { + "disable-all": true, + "enable": [ + "govet", + "golint", + "goimports", + "misspell", + "ineffassign", + "gofmt", + "whitespace" + ] + }, + "run": { + "skip-files": [ + "/zz_generated_" + ], + "deadline": "5m" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 0771198..a090196 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,33 @@ TODO ## License ## This library was originally developed by Reaction Engineering International for several internal projects. It is now being made available under the MIT license (see LICENSE.txt). + +## Mocking ## +You can update or generate all mocks for testing with: +``` +go generate ./... +``` + + +## Testing ## +The Framework uses the builtin testing capabilities in go + +## DB Migrations ## +All db migrations are handled using sql-migrate found at https://github.com/rubenv/sql-migrate. When using this as a library, you can act upon the migrations +```go + db, err := sql.Open("postgres", dbString) //"root:P1p3sh0p@tcp(:3306)/localDB?parseTime=true" + if err != nil{ + log.Fatal(err) + } + defer db.Close() + + restLibMigrations := migrations.Postgres() + + migrate.SetTable("restlib_migrations") + n, err := migrate.Exec(db, "postgres", restLibMigrations, migrate.Up) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Applied %d migrations!\n", n) + +``` \ No newline at end of file diff --git a/cache/ObjectMemCache.go b/cache/ObjectMemCache.go deleted file mode 100644 index 89c6f66..0000000 --- a/cache/ObjectMemCache.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2019 Reaction Engineering International. All rights reserved. -// Use of this source code is governed by the MIT license in the file LICENSE.txt. - -package cache - -import ( - "encoding/json" - "github.com/patrickmn/go-cache" - - "time" -) - -/** -Define a struct for RepoMem for news -*/ -type ObjectMemCache struct { - //Store the cache - cache *cache.Cache -} - -//Provide a method to make a new AnimalRepoSql -func NewObjectMemCache() *ObjectMemCache { - - //Define a new repo - infoRepo := ObjectMemCache{ - cache: cache.New(time.Hour/2.0, time.Hour), - } - - //Return a point - return &infoRepo - -} - -//Provide a method to make a new AnimalRepoSql -func NewObjectMemCacheWithTime(interval time.Duration) *ObjectMemCache { - - //Define a new repo - infoRepo := ObjectMemCache{ - cache: cache.New(interval/2.0, interval), - } - - //Return a point - return &infoRepo - -} - -/** -Get all of the news -*/ -func (repo *ObjectMemCache) Get(key string, returnItem interface{}) { - - item, found := repo.cache.Get(key) - - if found { - - //Convert to json - jsonByte, err := json.Marshal(item) - - //If there is no error - if err != nil { - return - } - - //Now restore back - json.Unmarshal(jsonByte, &returnItem) - - } else { - returnItem = nil - } - -} - -/** -Get all of the news -*/ -func (repo *ObjectMemCache) Set(key string, item interface{}) error { - - //Now save it - repo.cache.SetDefault(key, item) - return nil - -} - -func (repo *ObjectMemCache) GetString(key string) (string, bool) { - - item, found := repo.cache.Get(key) - - if found { - return item.(string), true - } else { - return "", false - } - -} - -/** -Get all of the news -*/ -func (repo *ObjectMemCache) SetString(key string, value string) { - - //Now save it - repo.cache.SetDefault(key, value) - -} diff --git a/cache/ObjectCache.go b/cache/cache.go similarity index 61% rename from cache/ObjectCache.go rename to cache/cache.go index 5fe8973..333ba67 100644 --- a/cache/ObjectCache.go +++ b/cache/cache.go @@ -3,8 +3,10 @@ package cache -type ObjectCache interface { - Get(key string, item interface{}) +//go:generate mockgen -destination=../mocks/mock_cache.go -package=mocks github.com/reaction-eng/restlib/cache Cache + +type Cache interface { + Get(key string, item interface{}) bool Set(key string, item interface{}) error diff --git a/cache/memory.go b/cache/memory.go new file mode 100644 index 0000000..9dda4b0 --- /dev/null +++ b/cache/memory.go @@ -0,0 +1,66 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package cache + +//go:generate mockgen -destination=../mocks/mock_memory.go -package=mocks github.com/reaction-eng/restlib/cache RawMemoryCache + +import ( + "encoding/json" +) + +type Memory struct { + rawMemoryCache RawMemoryCache +} + +type RawMemoryCache interface { + Get(k string) (interface{}, bool) + SetDefault(k string, x interface{}) +} + +func NewMemory(rawMemoryCache RawMemoryCache) *Memory { + objectMemCache := Memory{ + rawMemoryCache, + } + + return &objectMemCache +} + +func (memory *Memory) Set(key string, item interface{}) error { + //Now save it + memory.rawMemoryCache.SetDefault(key, item) + return nil +} + +func (memory *Memory) SetString(key string, value string) { + //Now save it + memory.rawMemoryCache.SetDefault(key, value) +} + +func (memory *Memory) Get(key string, returnItem interface{}) bool { + item, found := memory.rawMemoryCache.Get(key) + + if found { + //Convert to json + jsonByte, err := json.Marshal(item) + + if err != nil { + return false + } + + //Now restore back + json.Unmarshal(jsonByte, &returnItem) + return true + } else { + return false + } +} + +func (memory *Memory) GetString(key string) (string, bool) { + item, found := memory.rawMemoryCache.Get(key) + + if found { + return item.(string), true + } + return "", false +} diff --git a/cache/memory_test.go b/cache/memory_test.go new file mode 100644 index 0000000..010bb18 --- /dev/null +++ b/cache/memory_test.go @@ -0,0 +1,162 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package cache + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/reaction-eng/restlib/mocks" + + "github.com/golang/mock/gomock" +) + +func TestNewMemory(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockMemoryCache := mocks.NewMockRawMemoryCache(mockCtrl) + + // act + memory := NewMemory(mockMemoryCache) + + // assert + assert.Equal(t, mockMemoryCache, memory.rawMemoryCache) +} + +func TestMemory_Set(t *testing.T) { + testCases := []struct { + key string + item interface{} + }{ + {"test 1", make([]int, 3)}, + {"test 1", "blue"}, + {"test 1", 23}, + {"test 1", &struct{}{}}, + } + + mockCtrl := gomock.NewController(t) + for _, testCase := range testCases { + // arrange + mockMemoryCache := mocks.NewMockRawMemoryCache(mockCtrl) + mockMemoryCache.EXPECT().SetDefault(testCase.key, testCase.item).Times(1) + + memory := NewMemory(mockMemoryCache) + + // act + memory.Set(testCase.key, testCase.item) + } + + // assert + mockCtrl.Finish() +} + +func TestMemory_SetString(t *testing.T) { + testCases := []struct { + key string + item string + }{ + {"test 1", "blue"}, + {"test 1", "blue green"}, + } + + mockCtrl := gomock.NewController(t) + for _, testCase := range testCases { + // arrange + mockMemoryCache := mocks.NewMockRawMemoryCache(mockCtrl) + mockMemoryCache.EXPECT().SetDefault(testCase.key, testCase.item).Times(1) + + memory := NewMemory(mockMemoryCache) + + // act + memory.SetString(testCase.key, testCase.item) + } + + // assert + mockCtrl.Finish() +} + +func TestMemory_Get(t *testing.T) { + testCases := []struct { + key string + get interface{} + found bool + expected *struct { + Value1 int + Value2 string + Value3 interface{} + } + }{ + {"test 1", struct { + Value1 int + Value2 string + Value3 interface{} + }{Value1: 21, Value2: "test", Value3: "test"}, + true, &struct { + Value1 int + Value2 string + Value3 interface{} + }{Value1: 21, Value2: "test", Value3: "test"}}, + {"test 1", "blue", false, nil}, + } + + mockCtrl := gomock.NewController(t) + for _, testCase := range testCases { + // arrange + mockMemoryCache := mocks.NewMockRawMemoryCache(mockCtrl) + mockMemoryCache.EXPECT().Get(testCase.key).Return(testCase.get, testCase.found).Times(1) + + memory := NewMemory(mockMemoryCache) + + // act + var result struct { + Value1 int + Value2 string + Value3 interface{} + } + + found := memory.Get(testCase.key, &result) + + assert.Equal(t, testCase.found, found) + if found { + assert.Equal(t, testCase.expected, &result) + } + } + + // assert + mockCtrl.Finish() +} + +func TestMemory_GetString(t *testing.T) { + testCases := []struct { + key string + get string + found bool + expected string + }{ + {"testKey", "", true, ""}, + {"testKey", "123", true, "123"}, + {"testKey", "123", false, ""}, + } + + mockCtrl := gomock.NewController(t) + for _, testCase := range testCases { + // arrange + mockMemoryCache := mocks.NewMockRawMemoryCache(mockCtrl) + mockMemoryCache.EXPECT().Get(testCase.key).Return(testCase.get, testCase.found).Times(1) + + memory := NewMemory(mockMemoryCache) + + // act + result, found := memory.GetString(testCase.key) + + assert.Equal(t, testCase.found, found) + assert.Equal(t, testCase.expected, result) + } + + // assert + mockCtrl.Finish() +} diff --git a/cache/ObjectRedisCache.go b/cache/redis.go similarity index 58% rename from cache/ObjectRedisCache.go rename to cache/redis.go index 588eca8..f9ab30e 100644 --- a/cache/ObjectRedisCache.go +++ b/cache/redis.go @@ -5,22 +5,17 @@ package cache import ( "encoding/json" + "time" + "github.com/go-redis/cache" "github.com/go-redis/redis" - "time" ) -/** -Define a struct for RepoMem for news -*/ -type ObjectRedisCache struct { - //Store the cache +type Redis struct { codec *cache.Codec } -//Provide a method to make a new AnimalRepoSql -func NewObjectRedisCache(redis *redis.Ring) *ObjectRedisCache { - +func NewObjectRedisCache(redis *redis.Ring) *Redis { codec := &cache.Codec{ Redis: redis, @@ -33,68 +28,47 @@ func NewObjectRedisCache(redis *redis.Ring) *ObjectRedisCache { } //Define a new repo - infoRepo := ObjectRedisCache{ + infoRepo := Redis{ codec: codec, } //Return a point return &infoRepo - } -/** -Get all of the news -*/ -func (repo *ObjectRedisCache) Get(key string, item interface{}) { - +func (repo *Redis) Get(key string, item interface{}) bool { //Get the summary err := repo.codec.Get(key, &item) if err != nil { item = nil + return false } - + return true } -/** -Get all of the news -*/ -func (repo *ObjectRedisCache) Set(key string, item interface{}) error { - - //Now save it +func (repo *Redis) Set(key string, item interface{}) error { return repo.codec.Set(&cache.Item{ Key: key, Object: item, Expiration: time.Hour, }) - } -func (repo *ObjectRedisCache) GetString(key string) (string, bool) { - - //Get the google item +func (repo *Redis) GetString(key string) (string, bool) { var item string - //Get the summary err := repo.codec.Get(key, &item) if err != nil { return "", false } - // - ////Now return the item - return item, true + return item, true } -/** -Get all of the news -*/ -func (repo *ObjectRedisCache) SetString(key string, value string) { - - //Now save it +func (repo *Redis) SetString(key string, value string) { repo.codec.Set(&cache.Item{ Key: key, Object: value, Expiration: time.Hour, }) - } diff --git a/configuration/Configuration.go b/configuration/Configuration.go deleted file mode 100644 index ba10a2e..0000000 --- a/configuration/Configuration.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2019 Reaction Engineering International. All rights reserved. -// Use of this source code is governed by the MIT license in the file LICENSE.txt. - -package configuration - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "os" - "strconv" - "strings" -) - -/** -Define a simple database configuration -*/ -type Configuration struct { - //Load in the Params from json - Params map[string]interface{} -} - -//Provide a function to create a new one -func NewConfiguration(configFiles ...string) (*Configuration, error) { - //Define a Configuration - config := Configuration{ - Params: make(map[string]interface{}, 0), - } - - // Read secrets last which will overwrite any existing keys - configFiles = append(configFiles, "config.secret.json") - - //Now march over each file - for _, configFile := range configFiles { - //See if itself a config - var testMap map[string]interface{} - err := json.Unmarshal([]byte(configFile), &testMap) - - //See if it is a config string - if err == nil && testMap != nil { - //Merge the maps - for k, v := range testMap { - config.Params[k] = v - } - - } else { - //Parse as file - //Load in the file - configFileStream, err := os.Open(configFile) - - if err == nil { - //Get the json and add to the Params - jsonParser := json.NewDecoder(configFileStream) - jsonParser.Decode(&config.Params) - configFileStream.Close() - } - } - - } - - //Return it - return &config, nil -} - -/** - * Add function to get item - */ -func (config *Configuration) Get(key string) interface{} { - //Get the key from the file - param := config.Params[key] - - //Now see if it is specified in the env - systemEnvVar := os.Getenv(key) - - //If it is not empty set it - if len(systemEnvVar) != 0 { - param = systemEnvVar - } - - return param - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetFatal(key string) interface{} { - //Get the thing - thing := config.Get(key) - - //Make sure it is not nil - if thing == nil { - log.Fatal("Cannot not find configuration for " + key) - } - - return thing - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetString(key string) string { - //Get the value - value := config.Get(key) - - if value == nil { - return "" - } else { - //Get the key from the - return fmt.Sprint(value) - } - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetStringError(key string) (string, error) { - //Get the value - value := config.Get(key) - - if value == nil { - return "", errors.New("could not find " + key) - } - - //Get the key from the - return fmt.Sprint(value), nil - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetStringFatal(key string) string { - //Get the key from the - return fmt.Sprint(config.GetFatal(key)) - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetInt(key string) (int, error) { - //Get the key from the - res, err := strconv.Atoi(config.GetString(key)) - - return res, err - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetIntFatal(key string) int { - - //Get the string - string := config.GetStringFatal(key) - - //Get the key from the - res, err := strconv.Atoi(string) - - if err != nil { - log.Fatal("Cannot not find int configuration for " + key) - - } - - return res - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetFloat(key string) (float64, error) { - //Get the key from the - res, err := strconv.ParseFloat(config.GetString(key), 64) - - return res, err - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetKeys() []string { - keys := make([]string, len(config.Params)) - - i := 0 - for k := range config.Params { - keys[i] = k - i++ - } - - return keys - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetConfig(key string) *Configuration { - //Get the child interface - childConfigInterface := config.Get(key) - - //If childConfigInterface, return nil - if childConfigInterface == nil { - return nil - } - - //Now cast it - childConfig := childConfigInterface.(map[string]interface{}) - - return &Configuration{childConfig} - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetStruct(key string, object interface{}) error { - //Get the child interface - childConfigInterface := config.Get(key) - - //If childConfigInterface, return nil - if childConfigInterface == nil { - return nil - } - - //Now unmarshal - jsonByte, err := json.Marshal(childConfigInterface) - - //If there is no error - if err != nil { - return err - } - - //Now put the json back into the object - err = json.Unmarshal(jsonByte, object) - - return err - -} - -/** - * Add function to get item - */ -func (config *Configuration) GetStringArray(key string) []string { - //Get the child interface - childConfigInterface := config.Get(key) - - //Get as an array - childArray := childConfigInterface.([]interface{}) - - //Now build a new slice - childStringArray := make([]string, 0) - - //Now march over each child array - for _, child := range childArray { - childStringArray = append(childStringArray, child.(string)) - - } - - return childStringArray - -} - -// GetBool returns a configuration entry typed as bool. - -// key is the configuration entry to retrieve. If the entry does not exist or -// is not bool, then defaultVal is returned. Values of type string that are -// some variant of true/false, True/False, etc. will be converted to bool. This -// also works for parameters retrieved from environment variables. -// -// It is the caller's responsibility to provide the correct default for the -// given use case. -func (config *Configuration) GetBool(key string, defaultVal bool) bool { - switch val := config.Get(key).(type) { - case bool: - return val - // Convert strings with true/false text to bool - case string: - switch strings.ToLower(val) { - case "true": - return true - case "false": - return false - } - } - return defaultVal -} diff --git a/configuration/configuration.go b/configuration/configuration.go new file mode 100644 index 0000000..bdb95be --- /dev/null +++ b/configuration/configuration.go @@ -0,0 +1,22 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package configuration + +//go:generate mockgen -destination=../mocks/mock_configuration.go -package=mocks github.com/reaction-eng/restlib/configuration Configuration + +type Configuration interface { + Get(key string) interface{} + GetFatal(key string) interface{} + GetString(key string) string + GetStringError(key string) (string, error) + GetStringFatal(key string) string + GetInt(key string) (int, error) + GetIntFatal(key string) int + GetFloat(key string) (float64, error) + GetKeys() []string + GetConfig(key string) Configuration + GetStruct(key string, object interface{}) error + GetStringArray(key string) []string + GetBool(key string, defaultVal bool) bool +} diff --git a/configuration/json.go b/configuration/json.go new file mode 100644 index 0000000..3953414 --- /dev/null +++ b/configuration/json.go @@ -0,0 +1,266 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package configuration + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "os" + "strconv" +) + +type Json struct { + //Load in the params from json + params map[string]interface{} + + fatal func(interface{}) +} + +func NewJsonFatal(configFiles ...string) *Json { + config, err := NewJson(configFiles...) + if err != nil { + log.Fatal(err) + } + return config +} + +func NewJson(configFiles ...string) (*Json, error) { + //Define a Configuration + config := Json{ + params: make(map[string]interface{}, 0), + fatal: func(i interface{}) { + log.Fatal(i) + }, + } + + //Now march over each file + for _, configFile := range configFiles { + //See if itself a config + var testMap map[string]interface{} + err := json.Unmarshal([]byte(configFile), &testMap) + + //See if it is a config string + if err == nil && testMap != nil { + //Merge the maps + for k, v := range testMap { + config.params[k] = v + } + + } else { + //Parse as file + //Load in the file + configFileStream, err := os.Open(configFile) + + if err == nil { + //Get the json and add to the params + jsonParser := json.NewDecoder(configFileStream) + jsonParser.Decode(&config.params) + configFileStream.Close() + } + } + + } + + //Return it + return &config, nil +} + +func (jsonConfig *Json) Get(key string) interface{} { + //Get the key from the file + param := jsonConfig.params[key] + + //Now see if it is specified in the env + systemEnvVar := os.Getenv(key) + + //If it is not empty set it + if len(systemEnvVar) != 0 { + param = systemEnvVar + } + + return param + +} + +func (jsonConfig *Json) GetFatal(key string) interface{} { + //Get the thing + thing := jsonConfig.Get(key) + + //Make sure it is not nil + if thing == nil { + jsonConfig.fatal("Cannot not find configuration for " + key) + } + + return thing + +} + +func (jsonConfig *Json) GetString(key string) string { + //Get the value + value := jsonConfig.Get(key) + + if value == nil { + return "" + } else { + //Get the key from the + return fmt.Sprint(value) + } + +} + +func (jsonConfig *Json) GetStringError(key string) (string, error) { + //Get the value + value := jsonConfig.Get(key) + + if value == nil { + return "", errors.New("could not find " + key) + } + + //Get the key from the + return fmt.Sprint(value), nil + +} + +func (jsonConfig *Json) GetStringFatal(key string) string { + //Get the key from the + return fmt.Sprint(jsonConfig.GetFatal(key)) + +} + +func (jsonConfig *Json) GetInt(key string) (int, error) { + intString, err := jsonConfig.GetStringError(key) + if err != nil { + return 0, err + } + + res, err := strconv.Atoi(intString) + + return res, err + +} + +func (jsonConfig *Json) GetIntFatal(key string) int { + + result, err := jsonConfig.GetInt(key) + + if err != nil { + jsonConfig.fatal("Cannot not find int configuration for " + key) + } + + return result +} + +func (jsonConfig *Json) GetFloat(key string) (float64, error) { + floatString, err := jsonConfig.GetStringError(key) + if err != nil { + return 0, err + } + + res, err := strconv.ParseFloat(floatString, 64) + return res, err +} + +func (jsonConfig *Json) GetKeys() []string { + keys := make([]string, len(jsonConfig.params)) + + i := 0 + for k := range jsonConfig.params { + keys[i] = k + i++ + } + + return keys + +} + +func (jsonConfig *Json) GetConfig(key string) Configuration { + //Get the child interface + childConfigInterface := jsonConfig.Get(key) + + //If childConfigInterface, return nil + if childConfigInterface == nil { + return nil + } + + //Now cast it + childConfig, isMap := childConfigInterface.(map[string]interface{}) + if !isMap { + return nil + } + + return &Json{childConfig, jsonConfig.fatal} + +} + +func (jsonConfig *Json) GetStruct(key string, object interface{}) error { + //Get the child interface + childConfigInterface := jsonConfig.Get(key) + + //If childConfigInterface, return nil + if childConfigInterface == nil { + return errors.New("cannot not find int configuration for " + key) + } + + //Now unmarshal + jsonByte, err := json.Marshal(childConfigInterface) + + //If there is no error + if err != nil { + return err + } + + //Now put the json back into the object + err = json.Unmarshal(jsonByte, object) + + return err +} + +func (jsonConfig *Json) GetStringArray(key string) []string { + //Get the child interface + childConfigInterface := jsonConfig.Get(key) + + //Get as an array + childArray, isArray := childConfigInterface.([]interface{}) + if !isArray { + return nil + } + + //Now build a new slice + childStringArray := make([]string, 0) + + //Now march over each child array + for _, child := range childArray { + childStringArray = append(childStringArray, fmt.Sprint(child)) + } + + return childStringArray + +} + +// GetBool returns a configuration entry typed as bool. + +// key is the configuration entry to retrieve. If the entry does not exist or +// is not bool, then defaultVal is returned. Values of type string that are +// some variant of true/false, True/False, etc. will be converted to bool. This +// also works for parameters retrieved from environment variables. +// +// It is the caller's responsibility to provide the correct default for the +// given use case. +func (jsonConfig *Json) GetBool(key string, defaultVal bool) bool { + value := jsonConfig.Get(key) + if value == nil { + return defaultVal + } + + if boolValue, isBool := value.(bool); isBool { + return boolValue + } + + if stringValue, err := strconv.ParseBool(fmt.Sprint(value)); err == nil { + return stringValue + } + + return defaultVal +} diff --git a/configuration/json_test.go b/configuration/json_test.go new file mode 100644 index 0000000..d7c52fa --- /dev/null +++ b/configuration/json_test.go @@ -0,0 +1,436 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package configuration + +import ( + "errors" + "fmt" + "os" + "sort" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJson_Get(t *testing.T) { + testCases := []struct { + configString string + key string + expectedResult interface{} + }{ + {`{"testKey":true}`, "testKey", true}, + {`{"testKey":false}`, "testKey", false}, + {`{"test Key":"blue string"}`, "test Key", "blue string"}, + {`{"testKey":"blue string"}`, "testKeyNotFound", nil}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, err := NewJson(testCase.configString) + assert.Nil(t, err) + + // act + result := jsonConfig.Get(testCase.key) + + // assert + assert.Equal(t, testCase.expectedResult, result) + } + + // assert that the env takes over + for _, testCase := range testCases { + // arrange + jsonConfig, err := NewJson(testCase.configString) + assert.Nil(t, err) + os.Setenv(testCase.key, "test value 123") + + // act + result := jsonConfig.Get(testCase.key) + + // assert + assert.Equal(t, "test value 123", result) + + os.Unsetenv(testCase.key) + } +} + +func TestJson_GetFatal(t *testing.T) { + testCases := []struct { + configString string + key string + expectedResult interface{} + fatalMessage string + }{ + {`{"testKey":true}`, "testKey", true, ""}, + {`{"testKey":false}`, "testKey", false, ""}, + {`{"test Key":"blue string"}`, "test Key", "blue string", ""}, + {`{"testKey":"blue string"}`, "testKeyNotFound", nil, "Cannot not find configuration for testKeyNotFound"}, + {`{}`, "testKeyNotFound", nil, "Cannot not find configuration for testKeyNotFound"}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, err := NewJson(testCase.configString) + assert.Nil(t, err) + + var fatalMessage string + jsonConfig.fatal = func(i interface{}) { + fatalMessage = fmt.Sprint(i) + } + + // act + result := jsonConfig.GetFatal(testCase.key) + + // assert + assert.Equal(t, testCase.expectedResult, result) + assert.Equal(t, testCase.fatalMessage, fatalMessage) + } +} + +func TestJson_GetString(t *testing.T) { + testCases := []struct { + configString string + key string + expected string + }{ + {`{"testKey":true}`, "testKey", "true"}, + {`{"testKey":false}`, "testKey", "false"}, + {`{"test Key":"blue string"}`, "test Key", "blue string"}, + {`{"testKey":"blue string"}`, "testKeyNotFound", ""}, + {`{}`, "testKeyNotFound", ""}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, err := NewJson(testCase.configString) + assert.Nil(t, err) + + // act + result := jsonConfig.GetString(testCase.key) + + // assert + assert.Equal(t, testCase.expected, result) + } +} + +func TestJson_GetError(t *testing.T) { + testCases := []struct { + configString string + key string + expected string + returnError bool + }{ + {`{"testKey":true}`, "testKey", "true", false}, + {`{"testKey":false}`, "testKey", "false", false}, + {`{"test Key":"blue string"}`, "test Key", "blue string", false}, + {`{"testKey":"blue string"}`, "testKeyNotFound", "", true}, + {`{"testKey":"blue string"}`, "testkey", "", true}, + {`{}`, "testKeyNotFound", "", true}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + result, err := jsonConfig.GetStringError(testCase.key) + + // assert + assert.Equal(t, testCase.expected, result) + assert.Equal(t, err != nil, testCase.returnError) + } +} + +func TestJson_GetStringFatal(t *testing.T) { + testCases := []struct { + configString string + key string + expectedResult string + fatalMessage string + }{ + {`{"testKey":true}`, "testKey", "true", ""}, + {`{"testKey":false}`, "testKey", "false", ""}, + {`{"test Key":"blue string"}`, "test Key", "blue string", ""}, + {`{"testKey":"blue string"}`, "testKeyNotFound", "", "Cannot not find configuration for testKeyNotFound"}, + {`{}`, "testKeyNotFound", "", "Cannot not find configuration for testKeyNotFound"}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, err := NewJson(testCase.configString) + assert.Nil(t, err) + + var fatalMessage string + jsonConfig.fatal = func(i interface{}) { + fatalMessage = fmt.Sprint(i) + } + + // act + result := jsonConfig.GetStringFatal(testCase.key) + + // assert + assert.Equal(t, testCase.expectedResult, result) + assert.Equal(t, testCase.fatalMessage, fatalMessage) + } +} + +func TestJson_GetInt(t *testing.T) { + testCases := []struct { + configString string + key string + expected int + error error + }{ + {`{"testKey":true}`, "testKey", 0, &strconv.NumError{}}, + {`{"testKey":false}`, "testKey", 0, &strconv.NumError{}}, + {`{"test Key":"23"}`, "test Key", 23, nil}, + {`{"test Key":23}`, "test Key", 23, nil}, + {`{"test Key":"alpha beta"}`, "test Key", 0, &strconv.NumError{}}, + {`{"testKey":"32"}`, "testKeyNotFound", 0, errors.New("could not find testKeyNotFound")}, + {`{}`, "testKeyNotFound", 0, errors.New("could not find testKeyNotFound")}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + result, err := jsonConfig.GetInt(testCase.key) + + // assert + assert.Equal(t, testCase.expected, result) + assert.IsType(t, testCase.error, err) + } +} + +func TestJson_GetIntFatal(t *testing.T) { + testCases := []struct { + configString string + key string + expected int + fatalMessage string + }{ + {`{"testKey":true}`, "testKey", 0, "Cannot not find int configuration for testKey"}, + {`{"testKey":false}`, "testKey", 0, "Cannot not find int configuration for testKey"}, + {`{"test Key":"23"}`, "test Key", 23, ""}, + {`{"test Key":23}`, "test Key", 23, ""}, + {`{"test Key":"alpha beta"}`, "test Key", 0, "Cannot not find int configuration for test Key"}, + {`{"testKey":"32"}`, "testKeyNotFound", 0, "Cannot not find int configuration for testKeyNotFound"}, + {`{}`, "testKeyNotFound", 0, "Cannot not find int configuration for testKeyNotFound"}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + var fatalMessage string + jsonConfig.fatal = func(i interface{}) { + fatalMessage = fmt.Sprint(i) + } + + // act + result := jsonConfig.GetIntFatal(testCase.key) + + // assert + assert.Equal(t, testCase.expected, result) + assert.IsType(t, testCase.fatalMessage, fatalMessage) + } +} + +func TestJson_GetFloat(t *testing.T) { + testCases := []struct { + configString string + key string + expected float64 + error error + }{ + {`{"testKey":true}`, "testKey", 0, &strconv.NumError{}}, + {`{"testKey":false}`, "testKey", 0, &strconv.NumError{}}, + {`{"test Key":"23.43"}`, "test Key", 23.43, nil}, + {`{"test Key":23.23}`, "test Key", 23.23, nil}, + {`{"test Key":32.3E-2}`, "test Key", 32.3e-2, nil}, + {`{"test Key":"alpha beta"}`, "test Key", 0, &strconv.NumError{}}, + {`{"testKey":"32.3E-2"}`, "testKeyNotFound", 0, errors.New("")}, + {`{}`, "testKeyNotFound", 0, errors.New("")}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + result, err := jsonConfig.GetFloat(testCase.key) + + // assert + assert.Equal(t, testCase.expected, result) + assert.IsType(t, testCase.error, err) + } +} + +func TestJson_GetKeys(t *testing.T) { + testCases := []struct { + configString string + expected []string + }{ + {`{"testKey":true}`, []string{"testKey"}}, + {`{"testKey1":true, "test key2":true}`, []string{"testKey1", "test key2"}}, + {`{}`, []string{}}, + {`{"testKey1":true, "test key2":true, "testKey3":{"testkey4":4, "testkey5":5}}`, []string{"testKey1", "test key2", "testKey3"}}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + result := jsonConfig.GetKeys() + + // assert + sort.Strings(testCase.expected) + sort.Strings(result) + assert.Equal(t, testCase.expected, result) + } +} + +func TestJson_GetConfig(t *testing.T) { + testCases := []struct { + configString string + key string + expectedConfig string + }{ + {`{"testKey":true}`, "testKey", ""}, + {`{"testKey1":true, "test key2":true}`, "testKey1", ""}, + {`{}`, "testKey", ""}, + {`{"testKey1":true, "test key2":true, "testKey3":{"testkey4":4, "testkey5":5}}`, "testKey3", `{"testkey4":4, "testkey5":5}`}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + result := jsonConfig.GetConfig(testCase.key) + + // assert + var expected Configuration + if len(testCase.expectedConfig) > 0 { + expected, _ = NewJson(testCase.expectedConfig) + } + if result == nil { + assert.Equal(t, expected, result) + } else { + expectedJson, ok := expected.(*Json) + assert.True(t, ok) + resultJson, ok := result.(*Json) + assert.True(t, ok) + assert.Equal(t, expectedJson.params, resultJson.params) + } + } +} + +func TestJson_GetStruct(t *testing.T) { + testCases := []struct { + configString string + key string + expected interface{} + error error + }{ + {`{"testKey":true}`, "testKey", true, nil}, + {`{"testKey":true}`, "testKey1", nil, errors.New("")}, + {`{"testKey1":true, "test key2":true, "testKey3":{"testkey4":4.5, "testkey5":5.5}}`, "testKey3", map[string]interface{}{"testkey4": 4.5, "testkey5": 5.5}, nil}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + var result interface{} + err := jsonConfig.GetStruct(testCase.key, &result) + + // assert + assert.IsType(t, testCase.error, err) + assert.Equal(t, testCase.expected, result) + } +} + +func TestJson_GetStringArray(t *testing.T) { + testCases := []struct { + configString string + key string + expected []string + }{ + {`{"testKey":true}`, "testKey", nil}, + {`{"testKey":[true, false]}`, "testKey", []string{"true", "false"}}, + {`{"testKey":["hi there", "bye there"]}`, "testKey", []string{"hi there", "bye there"}}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + result := jsonConfig.GetStringArray(testCase.key) + + // assert + assert.Equal(t, testCase.expected, result) + } +} + +func TestJson_GetBool(t *testing.T) { + testCases := []struct { + configString string + key string + defaultVal bool + expected bool + }{ + {`{"testKey":true}`, "testKey", false, true}, + {`{"testKey":false}`, "testKey", true, false}, + {`{}`, "testKey", false, false}, + {`{}`, "testKey", true, true}, + {`{"testKey":"T"}`, "testKey", false, true}, + {`{"testKey":"F"}`, "testKey", true, false}, + {`{"testKey":"True"}`, "testKey", false, true}, + {`{"testKey":"False"}`, "testKey", true, false}, + {`{"testKey":"TRUE"}`, "testKey", false, true}, + {`{"testKey":"FALSE"}`, "testKey", true, false}, + {`{"testKey":"1"}`, "testKey", false, true}, + {`{"testKey":"0"}`, "testKey", true, false}, + {`{"testKey":1}`, "testKey", false, true}, + {`{"testKey":0}`, "testKey", true, false}, + } + + // With no environmental variables + for _, testCase := range testCases { + // arrange + jsonConfig, jsonErr := NewJson(testCase.configString) + assert.Nil(t, jsonErr) + + // act + result := jsonConfig.GetBool(testCase.key, testCase.defaultVal) + + // assert + assert.Equal(t, testCase.expected, result, testCase.configString) + } +} diff --git a/configuration/SQL.go b/configuration/sql.go similarity index 52% rename from configuration/SQL.go rename to configuration/sql.go index 8d04403..3c9f0b4 100644 --- a/configuration/SQL.go +++ b/configuration/sql.go @@ -5,27 +5,37 @@ package configuration import "fmt" +type Sql struct { + Configuration +} + +func NewSql(configuration Configuration) *Sql { + return &Sql{ + configuration, + } +} + //Build the dbString //username:password@protocol(address)/dbname -func (config *Configuration) GetMySqlDataBaseSourceName() string { +func (sqlConfig Sql) GetMySqlDataBaseSourceName() string { dbString := fmt.Sprintf("%s:%s@%s(%s)/%s?parseTime=true", - config.GetString("db_username"), - config.GetString("db_password"), - config.GetString("db_protocol"), - config.GetString("db_address"), - config.GetString("db_name"), + sqlConfig.GetString("db_username"), + sqlConfig.GetString("db_password"), + sqlConfig.GetString("db_protocol"), + sqlConfig.GetString("db_address"), + sqlConfig.GetString("db_name"), ) return dbString ////"root:P1p3sh0p@tcp(:3306)/localDB?parseTime=true" } //Build the dbString //username:password@protocol(address)/dbname -func (config *Configuration) GetPostgresDataBaseSourceName() string { +func (sqlConfig Sql) GetPostgresDataBaseSourceName() string { //dbString := "postgres://postgres:kOVGMnoS3iIk@localhost/postgres?sslmode=disable" dbString := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", - config.GetString("db_username"), - config.GetString("db_password"), - config.GetString("db_address"), - config.GetString("db_name"), + sqlConfig.GetString("db_username"), + sqlConfig.GetString("db_password"), + sqlConfig.GetString("db_address"), + sqlConfig.GetString("db_name"), ) return dbString diff --git a/configuration/sql_test.go b/configuration/sql_test.go new file mode 100644 index 0000000..676a2fb --- /dev/null +++ b/configuration/sql_test.go @@ -0,0 +1,63 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package configuration_test + +import ( + "testing" + + "github.com/reaction-eng/restlib/configuration" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/stretchr/testify/assert" +) + +func TestNewSql(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + + // act + sqlConfig := configuration.NewSql(mockConfiguration) + + //assert + assert.Equal(t, mockConfiguration, sqlConfig.Configuration) +} + +func TestSql_GetMySqlDataBaseSourceName(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetString("db_username").Times(1).Return("DBUSERNAME") + mockConfiguration.EXPECT().GetString("db_password").Times(1).Return("DBPASSWORD") + mockConfiguration.EXPECT().GetString("db_protocol").Times(1).Return("DBPROTOCOL") + mockConfiguration.EXPECT().GetString("db_address").Times(1).Return("DBADDRESS") + mockConfiguration.EXPECT().GetString("db_name").Times(1).Return("DBNAME") + + sqlConfig := configuration.NewSql(mockConfiguration) + + // act + dbString := sqlConfig.GetMySqlDataBaseSourceName() + + //assert + assert.Equal(t, "DBUSERNAME:DBPASSWORD@DBPROTOCOL(DBADDRESS)/DBNAME?parseTime=true", dbString) +} + +func TestSql_GetPostgresDataBaseSourceName(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetString("db_username").Times(1).Return("DBUSERNAME") + mockConfiguration.EXPECT().GetString("db_password").Times(1).Return("DBPASSWORD") + mockConfiguration.EXPECT().GetString("db_address").Times(1).Return("DBADDRESS") + mockConfiguration.EXPECT().GetString("db_name").Times(1).Return("DBNAME") + + sqlConfig := configuration.NewSql(mockConfiguration) + + // act + dbString := sqlConfig.GetPostgresDataBaseSourceName() + + //assert + assert.Equal(t, "postgres://DBUSERNAME:DBPASSWORD@DBADDRESS/DBNAME?sslmode=disable", dbString) +} diff --git a/email/Interface.go b/email/Interface.go deleted file mode 100644 index 4b41d5a..0000000 --- a/email/Interface.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2019 Reaction Engineering International. All rights reserved. -// Use of this source code is governed by the MIT license in the file LICENSE.txt. - -package email - -import "github.com/reaction-eng/restlib/utils" - -/** -Simple struct for email -*/ -type HeaderInfo struct { - To []string - Bcc []string - Subject string - ReplyTo string -} - -/** -Simple email message -*/ -type Interface interface { - - /** - Get the specific news istem - */ - SendEmail(email *HeaderInfo, body string, attachments map[string][]*utils.Base64File) error - - /** - Send html email - */ - SendEmailTemplateString(email *HeaderInfo, templateString string, data interface{}, attachments map[string][]*utils.Base64File) error - SendEmailTemplateFile(email *HeaderInfo, templateString string, data interface{}, attachments map[string][]*utils.Base64File) error - - /** - Send html email - */ - SendEmailTable(email *HeaderInfo, tableData TableInfo, attachments map[string][]*utils.Base64File) error -} - -/** -Simple email message -*/ -type TableInfo interface { - - //Check to see if it node - IsNode() bool - - //Get the title - GetTitle() string - - //Get value - GetValue() string - - //Get the children - GetChildren() []TableInfo -} diff --git a/email/emailer.go b/email/emailer.go new file mode 100644 index 0000000..1c1921c --- /dev/null +++ b/email/emailer.go @@ -0,0 +1,39 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package email + +//go:generate mockgen -destination=../mocks/mock_emailer.go -package=mocks github.com/reaction-eng/restlib/email Emailer,TableInfo + +import "github.com/reaction-eng/restlib/utils" + +type HeaderInfo struct { + To []string + Bcc []string + Subject string + ReplyTo string +} + +type Emailer interface { + Send(email *HeaderInfo, body string, attachments map[string][]*utils.Base64File) error + + SendTemplateString(email *HeaderInfo, templateString string, data interface{}, attachments map[string][]*utils.Base64File) error + SendTemplateFile(email *HeaderInfo, templateString string, data interface{}, attachments map[string][]*utils.Base64File) error + + SendTable(email *HeaderInfo, tableData TableInfo, attachments map[string][]*utils.Base64File) error +} + +type TableInfo interface { + + //Check to see if it node + IsNode() bool + + //Get the title + GetTitle() string + + //Get value + GetValue() string + + //Get the children + GetChildren() []TableInfo +} diff --git a/email/mailyak.go b/email/mailyak.go new file mode 100644 index 0000000..2f5771d --- /dev/null +++ b/email/mailyak.go @@ -0,0 +1,33 @@ +package email + +import ( + "io" + "net/smtp" + + "github.com/domodwyer/mailyak" +) + +type MailYakSmtpConnection struct { +} + +func NewMailYakSmtpConnection() *MailYakSmtpConnection { + return &MailYakSmtpConnection{} +} + +func (*MailYakSmtpConnection) New(smtpServer string, smtpUser string, smtpPassword string, smtpFrom string, smtpPort string) Mail { + return &MailYak{ + mailyak.New(smtpServer+smtpPort, smtp.PlainAuth("", smtpUser, smtpPassword, smtpServer)), + } +} + +type MailYak struct { + *mailyak.MailYak +} + +func (m *MailYak) Html() io.Writer { + return m.HTML() +} + +func (m *MailYak) SetPlain(body string) { + m.Plain().Set(body) +} diff --git a/email/smtpConnection.go b/email/smtpConnection.go new file mode 100644 index 0000000..7c9c740 --- /dev/null +++ b/email/smtpConnection.go @@ -0,0 +1,24 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package email + +//go:generate mockgen -destination=../mocks/mock_smtpConnection.go -package=mocks github.com/reaction-eng/restlib/email SmtpConnection,Mail + +import "io" + +type SmtpConnection interface { + New(smtpServer string, smtpUser string, smtpPassword string, smtpFrom string, smtpPort string) Mail +} + +type Mail interface { + To(addrs ...string) + Bcc(addrs ...string) + Subject(sub string) + From(addr string) + ReplyTo(addr string) + Html() io.Writer + SetPlain(body string) + Send() error + Attach(name string, r io.Reader) +} diff --git a/email/SmtpSender.go b/email/smtpEmailer.go similarity index 65% rename from email/SmtpSender.go rename to email/smtpEmailer.go index 673ad6d..51e0d47 100644 --- a/email/SmtpSender.go +++ b/email/smtpEmailer.go @@ -5,57 +5,47 @@ package email import ( "encoding/json" - "github.com/domodwyer/mailyak" - "github.com/reaction-eng/restlib/configuration" - "github.com/reaction-eng/restlib/utils" "html/template" - "log" - "net/smtp" "path/filepath" + "strings" "time" + + "github.com/reaction-eng/restlib/configuration" + "github.com/reaction-eng/restlib/utils" ) -/** -Simple struct for email -*/ -type SmtpSender struct { - smtpServer string - smtpUser string - smtpPassword string - smtpFrom string - smtpPort string +type SmtpEmailer struct { + smtpServer string + smtpUser string + smtpPassword string + smtpFrom string + smtpPort string + smtpConnectionManager SmtpConnection } -//Provide a method to make a new AnimalRepoSql -func NewSmtpSender(configFile ...string) *SmtpSender { - - //Load up the config - config, err := configuration.NewConfiguration(configFile...) - - if err != nil { - log.Fatal(err) - } +func NewSmtpEmailer(configuration configuration.Configuration, smtpConnectionManager SmtpConnection) *SmtpEmailer { - sender := SmtpSender{ - smtpServer: config.GetStringFatal("smtp_server"), - smtpPort: config.GetStringFatal("smtp_port"), - smtpUser: config.GetStringFatal("smtp_user"), - smtpPassword: config.GetStringFatal("smtp_password"), - smtpFrom: config.GetStringFatal("smtp_from"), + sender := SmtpEmailer{ + smtpServer: configuration.GetStringFatal("smtp_server"), + smtpPort: configuration.GetStringFatal("smtp_port"), + smtpUser: configuration.GetStringFatal("smtp_user"), + smtpPassword: configuration.GetStringFatal("smtp_password"), + smtpFrom: configuration.GetStringFatal("smtp_from"), + smtpConnectionManager: smtpConnectionManager, } return &sender - } -/** -Get all of the news -*/ -func (repo *SmtpSender) SendEmail(email *HeaderInfo, body string, attachments map[string][]*utils.Base64File) error { +func (smtpEmailer *SmtpEmailer) Send(email *HeaderInfo, body string, attachments map[string][]*utils.Base64File) error { - // Create a new email - specify the SMTP host and auth - mail := mailyak.New(repo.smtpServer+repo.smtpPort, - smtp.PlainAuth("", repo.smtpUser, repo.smtpPassword, repo.smtpServer)) //authentication + mail := smtpEmailer.smtpConnectionManager.New( + smtpEmailer.smtpServer, + smtpEmailer.smtpUser, + smtpEmailer.smtpPassword, + smtpEmailer.smtpFrom, + smtpEmailer.smtpPort, + ) //Set the to info mail.To(email.To...) @@ -64,17 +54,23 @@ func (repo *SmtpSender) SendEmail(email *HeaderInfo, body string, attachments ma mail.Bcc(email.Bcc...) } mail.Subject(email.Subject) - mail.From(repo.smtpFrom) + mail.From(smtpEmailer.smtpFrom) if len(email.ReplyTo) > 0 { mail.ReplyTo(email.ReplyTo) } //Set the body - mail.Plain().Set(body) + mail.SetPlain(body) + + for _, values := range attachments { + for _, value := range values { + //Save it to the mail + mail.Attach(value.GetName(), value.GetDataReader()) + } + } //Now Send return mail.Send() - } func formatInTimeZone(dateTime *time.Time, timeZone string, format string) string { @@ -90,13 +86,14 @@ func formatInTimeZone(dateTime *time.Time, timeZone string, format string) strin } -/** -Get all of the news -*/ -func (repo *SmtpSender) SendEmailTemplateString(email *HeaderInfo, templateString string, data interface{}, attachments map[string][]*utils.Base64File) error { - // Create a new email - specify the SMTP host and auth - mail := mailyak.New(repo.smtpServer+repo.smtpPort, - smtp.PlainAuth("", repo.smtpUser, repo.smtpPassword, repo.smtpServer)) //authentication +func (smtpEmailer *SmtpEmailer) SendTemplateString(email *HeaderInfo, templateString string, data interface{}, attachments map[string][]*utils.Base64File) error { + mail := smtpEmailer.smtpConnectionManager.New( + smtpEmailer.smtpServer, + smtpEmailer.smtpUser, + smtpEmailer.smtpPassword, + smtpEmailer.smtpFrom, + smtpEmailer.smtpPort, + ) //Set the to info mail.To(email.To...) @@ -105,7 +102,7 @@ func (repo *SmtpSender) SendEmailTemplateString(email *HeaderInfo, templateStrin mail.Bcc(email.Bcc...) } mail.Subject(email.Subject) - mail.From(repo.smtpFrom) + mail.From(smtpEmailer.smtpFrom) if len(email.ReplyTo) > 0 { mail.ReplyTo(email.ReplyTo) } @@ -126,14 +123,13 @@ func (repo *SmtpSender) SendEmailTemplateString(email *HeaderInfo, templateStrin } //Now add the html table - err = t.Execute(mail.HTML(), data) + err = t.Execute(mail.Html(), data) if err != nil { return err } - //Set an error tryJsonString, _ := json.Marshal(data) - mail.Plain().Set(string(tryJsonString)) + mail.SetPlain(string(tryJsonString)) //March over each attachment and add it for _, values := range attachments { @@ -150,10 +146,14 @@ func (repo *SmtpSender) SendEmailTemplateString(email *HeaderInfo, templateStrin /** Get all of the news */ -func (repo *SmtpSender) SendEmailTemplateFile(email *HeaderInfo, templateFile string, data interface{}, attachments map[string][]*utils.Base64File) error { - // Create a new email - specify the SMTP host and auth - mail := mailyak.New(repo.smtpServer+repo.smtpPort, - smtp.PlainAuth("", repo.smtpUser, repo.smtpPassword, repo.smtpServer)) //authentication +func (smtpEmailer *SmtpEmailer) SendTemplateFile(email *HeaderInfo, templateFile string, data interface{}, attachments map[string][]*utils.Base64File) error { + mail := smtpEmailer.smtpConnectionManager.New( + smtpEmailer.smtpServer, + smtpEmailer.smtpUser, + smtpEmailer.smtpPassword, + smtpEmailer.smtpFrom, + smtpEmailer.smtpPort, + ) //Set the to info mail.To(email.To...) @@ -164,7 +164,7 @@ func (repo *SmtpSender) SendEmailTemplateFile(email *HeaderInfo, templateFile st } mail.Subject(email.Subject) - mail.From(repo.smtpFrom) + mail.From(smtpEmailer.smtpFrom) if len(email.ReplyTo) > 0 { mail.ReplyTo(email.ReplyTo) } @@ -181,14 +181,14 @@ func (repo *SmtpSender) SendEmailTemplateFile(email *HeaderInfo, templateFile st return err } //Now add the html table - err = t.Execute(mail.HTML(), data) + err = t.Execute(mail.Html(), data) if err != nil { return err } //Set an error tryJsonString, _ := json.Marshal(data) - mail.Plain().Set(string(tryJsonString)) + mail.SetPlain(string(tryJsonString)) //March over each attachment and add it for _, values := range attachments { @@ -205,11 +205,14 @@ func (repo *SmtpSender) SendEmailTemplateFile(email *HeaderInfo, templateFile st /** Get all of the news */ -func (repo *SmtpSender) SendEmailTable(email *HeaderInfo, tableData TableInfo, attachments map[string][]*utils.Base64File) error { - - // Create a new email - specify the SMTP host and auth - mail := mailyak.New(repo.smtpServer+repo.smtpPort, - smtp.PlainAuth("", repo.smtpUser, repo.smtpPassword, repo.smtpServer)) //authentication +func (smtpEmailer *SmtpEmailer) SendTable(email *HeaderInfo, tableData TableInfo, attachments map[string][]*utils.Base64File) error { + mail := smtpEmailer.smtpConnectionManager.New( + smtpEmailer.smtpServer, + smtpEmailer.smtpUser, + smtpEmailer.smtpPassword, + smtpEmailer.smtpFrom, + smtpEmailer.smtpPort, + ) //Set the to info mail.To(email.To...) @@ -218,7 +221,7 @@ func (repo *SmtpSender) SendEmailTable(email *HeaderInfo, tableData TableInfo, a mail.Bcc(email.Bcc...) } mail.Subject(email.Subject) - mail.From(repo.smtpFrom) + mail.From(smtpEmailer.smtpFrom) if len(email.ReplyTo) > 0 { mail.ReplyTo(email.ReplyTo) } @@ -227,8 +230,8 @@ func (repo *SmtpSender) SendEmailTable(email *HeaderInfo, tableData TableInfo, a //Add the required functions t.Funcs(template.FuncMap{ - "GetTable": TableizeData, - "GetTitle": GetTableTitle, + "GetTable": tableizeData, + "GetTitle": getTableTitle, }) //Parse the file @@ -238,21 +241,19 @@ func (repo *SmtpSender) SendEmailTable(email *HeaderInfo, tableData TableInfo, a } //Now add the html table - err = t.Execute(mail.HTML(), tableData) + err = t.Execute(mail.Html(), tableData) if err != nil { return err } //Set an error - mail.Plain().Set("HTML Email Required") + mail.SetPlain("HTML Email Required") //March over each attachment and add it for _, values := range attachments { for _, value := range values { - //Save it to the mail mail.Attach(value.GetName(), value.GetDataReader()) - } } @@ -266,7 +267,7 @@ Function to tableize the data */ func getTableHtml() string { - return ` + return strings.ReplaceAll(strings.ReplaceAll(` @@ -298,14 +299,11 @@ func getTableHtml() string { - ` + `, "\n", ""), "\t", "") } -/** -Function to tableize the data -*/ -func GetTableTitle(args ...interface{}) string { +func getTableTitle(args ...interface{}) string { //check to see if it is a tableInfo tableInfo, ok := args[0].(TableInfo) @@ -319,10 +317,7 @@ func GetTableTitle(args ...interface{}) string { } -/** -Function to tableize the data -*/ -func TableizeData(args ...interface{}) template.HTML { +func tableizeData(args ...interface{}) template.HTML { //check to see if it is a tableInfo tableInfo, ok := args[0].(TableInfo) @@ -335,9 +330,6 @@ func TableizeData(args ...interface{}) template.HTML { } } -/** -Function to tableize the data -*/ func tableizeTalbeInfo(info TableInfo) string { //Check to see if it has children html := "" diff --git a/email/smtpEmailer_test.go b/email/smtpEmailer_test.go new file mode 100644 index 0000000..41dfc74 --- /dev/null +++ b/email/smtpEmailer_test.go @@ -0,0 +1,458 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package email_test + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "os" + "testing" + "text/template" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/reaction-eng/restlib/utils" + + "github.com/reaction-eng/restlib/email" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" +) + +func TestNewSmtpEmailer(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStringFatal("smtp_server").Return("SMTPSERVER").Times(1) + mockConfiguration.EXPECT().GetStringFatal("smtp_port").Return("SMTPPORT").Times(1) + mockConfiguration.EXPECT().GetStringFatal("smtp_user").Return("SMTPUSER").Times(1) + mockConfiguration.EXPECT().GetStringFatal("smtp_password").Return("SMTPPASSWORD").Times(1) + mockConfiguration.EXPECT().GetStringFatal("smtp_from").Return("SMTPFROM").Times(1) + + mockSmtpConnection := mocks.NewMockSmtpConnection(mockCtrl) + + // act + email.NewSmtpEmailer(mockConfiguration, mockSmtpConnection) + + // assert +} + +func TestSmtpEmailer_Send(t *testing.T) { + testCases := []struct { + header email.HeaderInfo + body string + attachments map[string][]*utils.Base64File + error error + }{ + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, "Email Body", nil, nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234"}, "Email Body", nil, nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234", ReplyTo: ""}, "Email Body", nil, nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234", ReplyTo: ""}, "Email Body", nil, errors.New("test error")}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234", ReplyTo: "Test One"}, "Email Body", map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, nil}, + } + + // arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStringFatal("smtp_server").Return("SMTPSERVER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_port").Return("SMTPPORT").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_user").Return("SMTPUSER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_password").Return("SMTPPASSWORD").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_from").Return("SMTPFROM").Times(len(testCases)) + + for _, testCase := range testCases { + // arrange + mockMail := mocks.NewMockMail(mockCtrl) + mockMail.EXPECT().To(testCase.header.To).Times(1) + bccCalled := 0 + if testCase.header.Bcc != nil { + bccCalled = 1 + } + mockMail.EXPECT().Bcc(testCase.header.Bcc).Times(bccCalled) + mockMail.EXPECT().Subject(testCase.header.Subject).Times(1) + mockMail.EXPECT().From("SMTPFROM").Times(1) + replyTo := 0 + if len(testCase.header.ReplyTo) > 0 { + replyTo = 1 + } + mockMail.EXPECT().ReplyTo(testCase.header.ReplyTo).Times(replyTo) + mockMail.EXPECT().SetPlain(testCase.body).Times(1) + + for _, values := range testCase.attachments { + for _, value := range values { + //Save it to the mail + mockMail.EXPECT().Attach(value.GetName(), value.GetDataReader()).Times(1) + } + } + + mockMail.EXPECT().Send().Times(1).Return(testCase.error) + + mockSmtpConnection := mocks.NewMockSmtpConnection(mockCtrl) + mockSmtpConnection.EXPECT().New("SMTPSERVER", "SMTPUSER", "SMTPPASSWORD", "SMTPFROM", "SMTPPORT").Times(1).Return(mockMail) + + emailer := email.NewSmtpEmailer(mockConfiguration, mockSmtpConnection) + + // act + err := emailer.Send(&testCase.header, testCase.body, testCase.attachments) + + assert.Equal(t, testCase.error, err) + } +} + +func TestSmtpEmailer_TemplateString(t *testing.T) { + + referenceTime := time.Unix(1574622126, 0) + + testCases := []struct { + header email.HeaderInfo + templateString string + data interface{} + expected string + attachments map[string][]*utils.Base64File + error error + }{ + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info2}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: 32

", + nil, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info2}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: 32

", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + `

ExampleInfo

{{.Info1}}

Email: {{formatInTimeZone .Info2 "America/Denver" "2006-01-02 03:04:05 PM"}}

`, + struct { + Info1 string + Info2 *time.Time + }{"alpha beta", &referenceTime}, + "

ExampleInfo

alpha beta

Email: 2019-11-24 12:02:06 PM

", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info2}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: 32

", + nil, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info3}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: ", + nil, + template.ExecError{}}, + } + + // arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStringFatal("smtp_server").Return("SMTPSERVER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_port").Return("SMTPPORT").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_user").Return("SMTPUSER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_password").Return("SMTPPASSWORD").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_from").Return("SMTPFROM").Times(len(testCases)) + + for _, testCase := range testCases { + // arrange + mockMail := mocks.NewMockMail(mockCtrl) + mockMail.EXPECT().To(testCase.header.To).Times(1) + bccCalled := 0 + if testCase.header.Bcc != nil { + bccCalled = 1 + } + mockMail.EXPECT().Bcc(testCase.header.Bcc).Times(bccCalled) + mockMail.EXPECT().Subject(testCase.header.Subject).Times(1) + mockMail.EXPECT().From("SMTPFROM").Times(1) + replyTo := 0 + if len(testCase.header.ReplyTo) > 0 { + replyTo = 1 + } + mockMail.EXPECT().ReplyTo(testCase.header.ReplyTo).Times(replyTo) + + // create a buffer for writing + var mockHtmlWriter bytes.Buffer + mockMail.EXPECT().Html().Times(1).Return(&mockHtmlWriter) + + for _, values := range testCase.attachments { + for _, value := range values { + //Save it to the mail + mockMail.EXPECT().Attach(value.GetName(), value.GetDataReader()).Times(1) + } + } + if testCase.error == nil { + tryJsonString, _ := json.Marshal(testCase.data) + mockMail.EXPECT().SetPlain(string(tryJsonString)).Times(1) + mockMail.EXPECT().Send().Times(1).Return(testCase.error) + } + mockSmtpConnection := mocks.NewMockSmtpConnection(mockCtrl) + mockSmtpConnection.EXPECT().New("SMTPSERVER", "SMTPUSER", "SMTPPASSWORD", "SMTPFROM", "SMTPPORT").Times(1).Return(mockMail) + + emailer := email.NewSmtpEmailer(mockConfiguration, mockSmtpConnection) + + // act + err := emailer.SendTemplateString(&testCase.header, testCase.templateString, testCase.data, testCase.attachments) + + assert.IsType(t, testCase.error, err) + assert.Equal(t, testCase.expected, mockHtmlWriter.String()) + } +} + +func TestSmtpEmailer_SendTemplateFile(t *testing.T) { + + referenceTime := time.Unix(1574622126, 0) + + testCases := []struct { + header email.HeaderInfo + templateString string + data interface{} + expected string + attachments map[string][]*utils.Base64File + error error + }{ + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info2}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: 32

", + nil, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info2}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: 32

", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + `

ExampleInfo

{{.Info1}}

Email: {{formatInTimeZone .Info2 "America/Denver" "2006-01-02 03:04:05 PM"}}

`, + struct { + Info1 string + Info2 *time.Time + }{"alpha beta", &referenceTime}, + "

ExampleInfo

alpha beta

Email: 2019-11-24 12:02:06 PM

", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info2}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: 32

", + nil, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 1234"}, + "

ExampleInfo

{{.Info1}}

Email: {{.Info3}}

", + struct { + Info1 string + Info2 int + }{"alpha beta", 32}, + "

ExampleInfo

alpha beta

Email: ", + nil, + template.ExecError{}}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + `

ExampleInfo

{{.Info1}}

Email: {{formatInTimeZone .Info2 "America/Denver" "2006-01-02 03:04:05 PM"}}

`, + struct { + Info1 string + Info2 *time.Time + }{"alpha beta", &referenceTime}, + "

ExampleInfo

alpha beta

Email: 2019-11-24 12:02:06 PM

", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + errors.New("send error")}, + } + + // arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStringFatal("smtp_server").Return("SMTPSERVER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_port").Return("SMTPPORT").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_user").Return("SMTPUSER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_password").Return("SMTPPASSWORD").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_from").Return("SMTPFROM").Times(len(testCases)) + + for _, testCase := range testCases { + // arrange + mockMail := mocks.NewMockMail(mockCtrl) + mockMail.EXPECT().To(testCase.header.To).Times(1) + bccCalled := 0 + if testCase.header.Bcc != nil { + bccCalled = 1 + } + mockMail.EXPECT().Bcc(testCase.header.Bcc).Times(bccCalled) + mockMail.EXPECT().Subject(testCase.header.Subject).Times(1) + mockMail.EXPECT().From("SMTPFROM").Times(1) + replyTo := 0 + if len(testCase.header.ReplyTo) > 0 { + replyTo = 1 + } + mockMail.EXPECT().ReplyTo(testCase.header.ReplyTo).Times(replyTo) + + // create a buffer for writing + var mockHtmlWriter bytes.Buffer + mockMail.EXPECT().Html().Times(1).Return(&mockHtmlWriter) + + for _, values := range testCase.attachments { + for _, value := range values { + //Save it to the mail + mockMail.EXPECT().Attach(value.GetName(), value.GetDataReader()).Times(1) + } + } + if _, isType := testCase.error.(template.ExecError); !isType { + tryJsonString, _ := json.Marshal(testCase.data) + mockMail.EXPECT().SetPlain(string(tryJsonString)).Times(1) + mockMail.EXPECT().Send().Times(1).Return(testCase.error) + } + mockSmtpConnection := mocks.NewMockSmtpConnection(mockCtrl) + mockSmtpConnection.EXPECT().New("SMTPSERVER", "SMTPUSER", "SMTPPASSWORD", "SMTPFROM", "SMTPPORT").Times(1).Return(mockMail) + + emailer := email.NewSmtpEmailer(mockConfiguration, mockSmtpConnection) + + // Save the html template to a temp file + tmpFile, tempFileError := ioutil.TempFile(os.TempDir(), "prefix-") + assert.Nil(t, tempFileError) + if _, tempFileError = tmpFile.Write([]byte(testCase.templateString)); tempFileError != nil { + assert.Nil(t, tempFileError) + } + if tempFileError = tmpFile.Close(); tempFileError != nil { + assert.Nil(t, tempFileError) + } + + // act + err := emailer.SendTemplateFile(&testCase.header, tmpFile.Name(), testCase.data, testCase.attachments) + + assert.IsType(t, testCase.error, err) + assert.Equal(t, testCase.expected, mockHtmlWriter.String()) + os.Remove(tmpFile.Name()) + } +} + +func buildMockTable(mockCtrl *gomock.Controller) *mocks.MockTableInfo { + mockChild := mocks.NewMockTableInfo(mockCtrl) + mockChild.EXPECT().GetTitle().Return("Example Child").Times(1) + mockChild.EXPECT().IsNode().Return(false).Times(1).Times(1) + mockChild.EXPECT().GetValue().Return("example child Value").Times(1) + mockChild.EXPECT().GetChildren().Times(0) + + mockTable := mocks.NewMockTableInfo(mockCtrl) + mockTable.EXPECT().GetTitle().Return("Example Table").Times(2) + mockTable.EXPECT().IsNode().Times(0) + mockTable.EXPECT().GetValue().Times(0) + mockTable.EXPECT().GetChildren().Return([]email.TableInfo{mockChild}) + + return mockTable +} + +func TestSmtpEmailer_SendTable(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + header email.HeaderInfo + table email.TableInfo + expected string + attachments map[string][]*utils.Base64File + error error + }{ + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123", ReplyTo: "reply"}, + buildMockTable(mockCtrl), + "

Example Table

Example Table
Example Child
example child Value
", + nil, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: nil, Subject: "test 123"}, + buildMockTable(mockCtrl), + "

Example Table

Example Table
Example Child
example child Value
", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 123"}, + buildMockTable(mockCtrl), + "

Example Table

Example Table
Example Child
example child Value
", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + nil}, + {email.HeaderInfo{To: []string{"ToString"}, Bcc: []string{"bccOne", "bbcTwo"}, Subject: "test 123"}, + buildMockTable(mockCtrl), + "

Example Table

Example Table
Example Child
example child Value
", + map[string][]*utils.Base64File{"testOne": {utils.NewBase64FileFromData("attachment1", []byte{1, 2, 3, 4})}}, + errors.New("send error")}, + } + + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStringFatal("smtp_server").Return("SMTPSERVER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_port").Return("SMTPPORT").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_user").Return("SMTPUSER").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_password").Return("SMTPPASSWORD").Times(len(testCases)) + mockConfiguration.EXPECT().GetStringFatal("smtp_from").Return("SMTPFROM").Times(len(testCases)) + + for _, testCase := range testCases { + // arrange + mockMail := mocks.NewMockMail(mockCtrl) + mockMail.EXPECT().To(testCase.header.To).Times(1) + bccCalled := 0 + if testCase.header.Bcc != nil { + bccCalled = 1 + } + mockMail.EXPECT().Bcc(testCase.header.Bcc).Times(bccCalled) + mockMail.EXPECT().Subject(testCase.header.Subject).Times(1) + mockMail.EXPECT().From("SMTPFROM").Times(1) + replyTo := 0 + if len(testCase.header.ReplyTo) > 0 { + replyTo = 1 + } + mockMail.EXPECT().ReplyTo(testCase.header.ReplyTo).Times(replyTo) + + // create a buffer for writing + var mockHtmlWriter bytes.Buffer + mockMail.EXPECT().Html().Times(1).Return(&mockHtmlWriter) + + for _, values := range testCase.attachments { + for _, value := range values { + //Save it to the mail + mockMail.EXPECT().Attach(value.GetName(), value.GetDataReader()).Times(1) + } + } + mockMail.EXPECT().SetPlain("HTML Email Required").Times(1) + mockMail.EXPECT().Send().Times(1).Return(testCase.error) + + mockSmtpConnection := mocks.NewMockSmtpConnection(mockCtrl) + mockSmtpConnection.EXPECT().New("SMTPSERVER", "SMTPUSER", "SMTPPASSWORD", "SMTPFROM", "SMTPPORT").Times(1).Return(mockMail) + + emailer := email.NewSmtpEmailer(mockConfiguration, mockSmtpConnection) + + // act + err := emailer.SendTable(&testCase.header, testCase.table, testCase.attachments) + + assert.IsType(t, testCase.error, err) + assert.Equal(t, testCase.expected, mockHtmlWriter.String()) + } +} diff --git a/file/Storage.go b/file/Storage.go deleted file mode 100644 index 61e4e88..0000000 --- a/file/Storage.go +++ /dev/null @@ -1,8 +0,0 @@ -package file - -import "io" - -type Storage interface { - GetArbitraryFile(id string) (io.ReadCloser, error) - PostArbitraryFile(fileName string, parent string, file io.Reader, mime string) (string, error) -} diff --git a/file/item.go b/file/item.go new file mode 100644 index 0000000..6aa44bd --- /dev/null +++ b/file/item.go @@ -0,0 +1,53 @@ +package file + +import ( + "time" +) + +type Item struct { + //Keep a boolean if it is a file + Id string `json:"id"` + + //Hold the item + Name string `json:"name"` + + //Hold if we should hide the item + HideListing bool `json:"hideListing"` + + //Keep a date if useful + Date *time.Time `json:"date"` + + Type string `json:"type"` + + //Keep the Preview + Preview string `json:"preview"` + + //Thumbnail Image + ThumbnailUrl string `json:"thumbnail"` + + //Also Keep the parent Id + ParentId string `json:"parentId"` +} + +// ByAge implements sort.Interface for []Person based on +// the Age field. +type ByDate []Item + +func (a ByDate) Len() int { return len(a) } +func (a ByDate) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByDate) Less(i, j int) bool { + //Get both dates + iDate := a[i].Date + jDate := a[j].Date + + //If + if iDate == nil && jDate == nil { + return false + } else if iDate == nil { + return true + } else if jDate == nil { + return false + } else { + return (*jDate).Before(*iDate) + } +} diff --git a/file/listing.go b/file/listing.go new file mode 100644 index 0000000..7c3bdfa --- /dev/null +++ b/file/listing.go @@ -0,0 +1,50 @@ +package file + +import ( + "time" +) + +type Listing struct { + //Keep a boolean if it is a file + Id string `json:"id"` + + //Hold the item + Name string `json:"name"` + + //Hold if we should hide the item + HideListing bool `json:"hideListing"` + + //Keep a date if useful + Date *time.Time `json:"date"` + + Listings []Listing `json:"listings"` + Items []Item `json:"items"` + + //Keep the parent Id, unless this is root and then it is null + ParentId string `json:"parentId"` +} + +func NewListing() *Listing { + return &Listing{ + Listings: make([]Listing, 0), + Items: make([]Item, 0), + } +} + +//Define a function +type ItemFunc func(item *Item) + +func (dir *Listing) ForEach(doOnFolder bool, do ItemFunc) { + if dir == nil { + return + } + + //March over each item in the dir + for _, item := range dir.Items { + do(&item) + } + + for _, subDir := range dir.Listings { + subDir.ForEach(doOnFolder, do) + } +} diff --git a/file/storage.go b/file/storage.go new file mode 100644 index 0000000..3136bbf --- /dev/null +++ b/file/storage.go @@ -0,0 +1,18 @@ +package file + +//go:generate mockgen -destination=../mocks/mock_storage.go -package=mocks github.com/reaction-eng/restlib/file Storage + +import "io" + +type Storage interface { + GetArbitraryFile(id string) (io.ReadCloser, error) + PostArbitraryFile(fileName string, parent string, file io.Reader, mime string) (string, error) + + BuildListing(dirId string, previewLength int, includeFilter func(fileType string) bool) (*Listing, error) + GetFilePreview(id string, previewLength int) string + GetFileThumbnailUrl(id string) string + GetFileHtml(id string) string + GetMostRecentFileInDir(dirId string) (io.ReadCloser, error) + GetFileAsInterface(id string, inter interface{}) error + GetFirstFileMatching(dirId string, name string) (io.ReadCloser, error) +} diff --git a/go.mod b/go.mod index 33d047a..a21cfd4 100644 --- a/go.mod +++ b/go.mod @@ -3,23 +3,40 @@ module github.com/reaction-eng/restlib go 1.12 require ( + cloud.google.com/go v0.56.0 // indirect + github.com/DATA-DOG/go-sqlmock v1.3.3 github.com/SherClockHolmes/webpush-go v1.1.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/domodwyer/mailyak v3.1.1+incompatible - github.com/fogleman/fauxgl v0.0.0-20190627205746-5ab08979c242 + github.com/fogleman/fauxgl v0.0.0-20200407182642-576996add3ee github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046 // indirect - github.com/go-sql-driver/mysql v1.4.1 - github.com/go-stack/stack v1.8.0 // indirect - github.com/golang/snappy v0.0.1 // indirect - github.com/gorilla/mux v1.7.3 - github.com/gorilla/websocket v1.4.0 // indirect + github.com/go-redis/cache v6.4.0+incompatible + github.com/go-redis/redis v6.15.6+incompatible + github.com/go-sql-driver/mysql v1.5.0 + github.com/gobuffalo/packr/v2 v2.7.1 + github.com/golang/mock v1.4.3 + github.com/golang/protobuf v1.4.0 // indirect + github.com/gorilla/mux v1.7.4 + github.com/gorilla/websocket v1.4.2 // indirect + github.com/klauspost/compress v1.10.5 // indirect + github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect + github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/nlopes/slack v0.5.0 - github.com/pkg/errors v0.8.1 // indirect - github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect + github.com/nlopes/slack v0.6.0 + github.com/onsi/ginkgo v1.10.3 // indirect + github.com/onsi/gomega v1.7.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125 + github.com/stretchr/testify v1.4.0 + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xdg/stringprep v1.0.0 // indirect - go.mongodb.org/mongo-driver v1.0.4 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - google.golang.org/api v0.7.0 + go.mongodb.org/mongo-driver v1.3.2 + golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 + golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect + google.golang.org/api v0.22.0 + google.golang.org/appengine v1.6.6 // indirect + google.golang.org/genproto v0.0.0-20200424135956-bca184e23272 // indirect + google.golang.org/grpc v1.29.1 // indirect ) diff --git a/go.sum b/go.sum index 9c96a81..0eb690a 100644 --- a/go.sum +++ b/go.sum @@ -2,115 +2,786 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/SherClockHolmes/webpush-go v1.1.0 h1:WjWbwo0Bf1Cbd8Yr0myrpYYlcN7VvQz/TVmUTjxL35g= github.com/SherClockHolmes/webpush-go v1.1.0/go.mod h1:Jbd13H6kOFZubRMAaEHQS+e0EpP/aSHtLKeo9gsyO5k= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0 h1:epsH3lb7KVbXHYk7LYGN5EiE0MxcevHU85CKITJ0wUY= +github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/domodwyer/mailyak v3.1.1+incompatible h1:oPtXn3+56LEFbdqH0bpuPRsqtijW9l2POpQe9sTUsSI= github.com/domodwyer/mailyak v3.1.1+incompatible/go.mod h1:5NNYkn9hxcdNEOmmMx0yultN5VLorZQ+AWQo9iya+UY= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fogleman/fauxgl v0.0.0-20190627205746-5ab08979c242 h1:MZ1TQZgNq5gVCn+8szWfaOD7v7rHTsK/omVdnI9DMyc= github.com/fogleman/fauxgl v0.0.0-20190627205746-5ab08979c242/go.mod h1:7f7F8EvO8MWvDx9sIoloOfZBCKzlWuZV/h3TjpXOO3k= +github.com/fogleman/fauxgl v0.0.0-20200407182642-576996add3ee h1:tR3sn0xyN7+oB3lOAwTSOHE+In94DsdO8kjs6szIX88= +github.com/fogleman/fauxgl v0.0.0-20200407182642-576996add3ee/go.mod h1:7f7F8EvO8MWvDx9sIoloOfZBCKzlWuZV/h3TjpXOO3k= github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046 h1:n3RPbpwXSFT0G8FYslzMUBDO09Ix8/dlqzvUkcJm4Jk= github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046/go.mod h1:KDwyDqFmVUxUmo7tmqXtyaaJMdGon06y8BD2jmh84CQ= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-redis/cache v6.4.0+incompatible h1:ZaeoZofvBZmMr8ZKxzFDmkoRTSp8sxHdJlB3e3T6GDA= +github.com/go-redis/cache v6.4.0+incompatible/go.mod h1:XNnMdvlNjcZvHjsscEozHAeOeSE5riG9Fj54meG4WT4= +github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg= +github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godror/godror v0.13.3 h1:4A5GLGAJTSuELw1NThqY5bINYB+mqrln+kF5C2vuyCs= +github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc= +github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU= +github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= +github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 h1:MNApn+Z+fIT4NPZopPfCc1obT6aY3SVM6DOctz1A9ZU= +github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-oci8 v0.0.7 h1:BBXYpvzPO43QNTLDEivPFteeFZ9nKA6JQ6eifpxOmio= +github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= +github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA= +github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubenv/sql-migrate v0.0.0-20191121092708-da1cb182f00e h1:vZzr5nI/hQHxNbZXX6jdFOUM63QGJExahnsYXt7Awug= +github.com/rubenv/sql-migrate v0.0.0-20191121092708-da1cb182f00e/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= +github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125 h1:TNreUp2iVj8BSG7NrBFUeq5UoAGK7fWas/Eb4jlwKlY= +github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.4 h1:bHxbjH6iwh1uInchXadI6hQR107KEbgYsMzoblDONmQ= go.mongodb.org/mongo-driver v1.0.4/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.2 h1:IYppNjEV/C+/3VPbhHVxQ4t04eVW0cLp0/pNdW++6Ug= +go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438 h1:khxRGsvPk4n2y8I/mLLjp7e5dMTJmH75wvqS6nMwUtY= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c h1:97SnQk1GYRXJgvwZ8fadnxDOWfKvkNQHH3CtZntPSrM= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200424135956-bca184e23272 h1:yKqICwsk6cvaHc7nFgdKRJU45wKUGve28MXBkX8nCTg= +google.golang.org/genproto v0.0.0-20200424135956-bca184e23272/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/google/DocumentHierarchy.go b/google/DocumentHierarchy.go deleted file mode 100644 index d1b279d..0000000 --- a/google/DocumentHierarchy.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2019 Reaction Engineering International. All rights reserved. -// Use of this source code is governed by the MIT license in the file LICENSE.txt. - -package google - -import ( - "encoding/json" - "errors" - "fmt" - "time" -) - -/** - * An interface type to hold a document or directory - */ -type Item interface { - GetId() string - GetName() string - GetDate() *time.Time -} - -//Hold a base type document -type File struct { - //Keep a boolean if it is a file - Id string `json:"id"` - - //Hold the item - Name string `json:"name"` - - //Hold if we should hide the item - HideListing bool `json:"hideListing"` - - //Keep a date if useful - Date *time.Time `json:"date"` -} - -//Hold a base type document -func (file File) GetId() string { - return file.Id -} -func (file File) GetName() string { - return file.Name -} -func (file File) GetDate() *time.Time { - return file.Date -} - -//Hold the structs need to create a tree -type Document struct { - File - - Type string `json:"type"` - - //Keep the Preview - Preview string `json:"preview"` - - //Thumbnail Image - ThumbnailUrl string `json:"thumbnail"` - - //Also Keep the parent id - ParentId string `json:"parentid"` -} - -func (dir Document) MarshalJSON() ([]byte, error) { - //Store a ddir copy - type fakeItem Document - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(dir), - "Document", - }) -} - -//Hold the struct need to create a tree -type Directory struct { - File - - //Keep the type for directory anyways - Type string `json:"type"` - - //Hold a list of items - Items []Item `json:"items"` - - //Keep the parent id, unless this is root and then it is null - ParentId string `json:"parentid"` -} - -/** -Custom marashaler -*/ -func (dir Directory) MarshalJSON() ([]byte, error) { - //Store a ddir copy - type fakeItem Directory - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(dir), - "Directory", - }) -} - -func (dir *Directory) UnmarshalJSON(b []byte) error { - // First, deserialize everything into a map of map - var objMap map[string]*json.RawMessage - err := json.Unmarshal(b, &objMap) - if err != nil { - return err - } - - //Extract a copy of the items - var rawItemsList []*json.RawMessage - err = json.Unmarshal(*objMap["items"], &rawItemsList) - if err != nil { - return err - } - - //Now remove the value - objMap["items"] = nil - - //type def dir - type dirTmp Directory - - //Now restore back - bytes, err := json.Marshal(objMap) - //And back into the object - err = json.Unmarshal(bytes, (*dirTmp)(dir)) - - //Now decode each of the items in the list - dir.Items = make([]Item, len(rawItemsList)) - - var m map[string]interface{} - for index, rawMessage := range rawItemsList { - err = json.Unmarshal(*rawMessage, &m) - if err != nil { - return err - } - - //Get the - InternalItemType := fmt.Sprint(m["InternalItemType"]) - - // Depending on the type, we can run json.Unmarshal again on the same byte slice - // But this time, we'll pass in the appropriate struct instead of a map - switch InternalItemType { - case "Document": - var tmp Document - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - case "Directory": - var tmp Directory - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - case "Form": - var tmp Form - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - case "Event": - var tmp Event - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - default: - return errors.New("Unsupported type found!") - } - } - - return nil -} - -//Define a function -type ItemFunc func(item Item) - -//Hold the struct need to create a tree -func (dir *Directory) MarchDownTree(doOnFolder bool, do ItemFunc) { - if dir == nil { - return - } - - //Do this to the item - if doOnFolder { - do(dir) - } - - //March over each item in the dir - for _, item := range dir.Items { - //If it is a dir, do a recurisive dive - if asDir, isDir := item.(*Directory); isDir { - asDir.MarchDownTree(doOnFolder, do) - } else { - //Just apply the function - do(item) - } - - } - -} - -//Store the forms metadata -type FormMetaData struct { - Title string `json:"title"` - - EmailTo []string `json:"emailTo"` - - EmailTemplate string `json:"emailTemplate"` - - EmailSubjectField string `json:"emailSubjectField"` - - DriveInfo []FormDriveInfo `json:"driveInfo"` - - RequiredPerm []string `json:"requiredPerm"` -} - -//Store the forms metadata -type FormDriveInfo struct { - SheetId string `json:"sheetId"` - SheetName string `json:"sheetName"` -} - -//Hold the struct need to create a tree -type Form struct { - File - //Keep the type for directory anyways - Type string `json:"type"` - - //Keep the parent id, unless this is root and then it is null - ParentId string `json:"parentid"` - - //Hold the item - Metadata FormMetaData `json:"metadata"` - - //Hold a list of items - JSONSchema map[string]interface{} `json:"JSONSchema"` - - //Keep the parent id, unless this is root and then it is null - UISchema map[string]interface{} `json:"UISchema"` -} - -func (dir Form) MarshalJSON() ([]byte, error) { - //Store a ddir copy - type fakeItem Form - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(dir), - "Form", - }) -} - -//Hold the struct need to create a tree -type Event struct { - File - - //Keep a boolean if it is a file - InfoId string `json:"infoId"` - - //Keep a boolean if it is a file - SignupId string `json:"signupId"` - - //Keep the parent id, unless this is root and then it is null - ParentId string `json:"parentid"` -} - -func (dir Event) MarshalJSON() ([]byte, error) { - //Store a ddir copy - type fakeItem Event - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(dir), - "Event", - }) -} - -// ByAge implements sort.Interface for []Person based on -// the Age field. -type ByDate []Item - -func (a ByDate) Len() int { return len(a) } -func (a ByDate) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByDate) Less(i, j int) bool { - //Get both dates - iDate := a[i].GetDate() - jDate := a[j].GetDate() - - //If - if iDate == nil && jDate == nil { - return false - } else if iDate == nil { - return true - } else if jDate == nil { - return false - } else { - return (*jDate).Before(*iDate) - } -} diff --git a/google/Sheets.go b/google/Sheets.go index a47e00b..744f82f 100644 --- a/google/Sheets.go +++ b/google/Sheets.go @@ -4,15 +4,16 @@ package google import ( - "github.com/reaction-eng/restlib/configuration" "errors" + "log" + "strconv" + "strings" + + "github.com/reaction-eng/restlib/configuration" "golang.org/x/net/context" "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" "google.golang.org/api/sheets/v4" - "log" - "strconv" - "strings" ) type Sheets struct { @@ -22,17 +23,14 @@ type Sheets struct { } //Get a new interface -func NewSheets(configFiles ...string) *Sheets { - //Create a new config - config, err := configuration.NewConfiguration(configFiles...) - +func NewSheets(configuation configuration.Configuration) *Sheets { //Create a new gInter := &Sheets{} //Open the client jwtConfig := &jwt.Config{ - Email: config.GetStringFatal("google_auth_email"), - PrivateKey: []byte(config.GetStringFatal("google_auth_key")), + Email: configuation.GetStringFatal("google_auth_email"), + PrivateKey: []byte(configuation.GetStringFatal("google_auth_key")), Scopes: []string{ sheets.DriveScope, sheets.DriveFileScope, @@ -138,7 +136,7 @@ func stringInSlice(a string, list []string) bool { } /** -Append to the sheet with the name and id split with a / +Append to the sheet with the name and Id split with a / */ func (sheetsCon *Sheets) AppendToSheetIdAndName(sheetIdAndName string, data interface{}) error { diff --git a/google/Drive.go b/google/drive.go similarity index 73% rename from google/Drive.go rename to google/drive.go index ee4cbfa..598d4c5 100644 --- a/google/Drive.go +++ b/google/drive.go @@ -7,19 +7,20 @@ import ( "encoding/base64" "encoding/json" "errors" - "github.com/reaction-eng/restlib/configuration" - "golang.org/x/net/context" - "golang.org/x/net/html" - "golang.org/x/oauth2/google" - "golang.org/x/oauth2/jwt" - "google.golang.org/api/drive/v3" "io" "io/ioutil" "log" - "path/filepath" "regexp" "strings" "time" + + "github.com/reaction-eng/restlib/configuration" + "github.com/reaction-eng/restlib/file" + "golang.org/x/net/context" + "golang.org/x/net/html" + "golang.org/x/oauth2/google" + "golang.org/x/oauth2/jwt" + "google.golang.org/api/drive/v3" ) type Drive struct { @@ -27,27 +28,35 @@ type Drive struct { //Store the connection to google. This has been wrapped with the correct Headers connection *drive.Service - //Store the preview length - previewLength int - //Store the timezone timeZone string } //Get a new interface -func NewDrive(configFiles ...string) *Drive { - //Create a new config - config, err := configuration.NewConfiguration(configFiles...) - +func NewDrive(configuration configuration.Configuration) (*Drive, error) { //Create a new + timeZone, err := configuration.GetStringError("default_time_zone") + if err != nil { + return nil, err + } + gInter := &Drive{ - timeZone: config.GetStringFatal("default_time_zone"), + timeZone: timeZone, + } + + email, err := configuration.GetStringError("google_auth_email") + if err != nil { + return nil, err + } + privateKey, err := configuration.GetStringError("google_auth_key") + if err != nil { + return nil, err } //Open the client jwtConfig := &jwt.Config{ - Email: config.GetStringFatal("google_auth_email"), - PrivateKey: []byte(config.GetStringFatal("google_auth_key")), + Email: email, + PrivateKey: []byte(privateKey), Scopes: []string{ drive.DriveMetadataReadonlyScope, drive.DriveReadonlyScope, @@ -63,13 +72,7 @@ func NewDrive(configFiles ...string) *Drive { driveConn, err := drive.New(httpCon) gInter.connection = driveConn - //Check for errors - if err != nil { - log.Fatalf("Unable to retrieve Drive client: %v", err) - } - - gInter.previewLength, _ = config.GetInt("preview_length") - return gInter + return gInter, err } //See if starts with a date and name @@ -131,10 +134,7 @@ func (gog *Drive) splitNameAndDate(nameIn string) (string, *time.Time) { } -/** -Recursive call to build the file list -*/ -func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFilter func(fileType string) bool) *Directory { +func (gog *Drive) BuildListing(dirId string, previewLength int, includeFilter func(fileType string) bool) (*file.Listing, error) { //Get this item folderInfo, err := gog.connection.Files. @@ -144,21 +144,16 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil //Return nothing from this folder if err != nil { - return nil + return nil, err } //Split the name and see if it is to be used name, date := gog.splitNameAndDate(folderInfo.Name) //Get all of the files in this folder - dir := &Directory{ - File: File{ - Id: folderInfo.Id, - Name: name, - }, - Type: folderInfo.MimeType, - Items: make([]Item, 0), - } + dir := file.NewListing() + dir.Id = folderInfo.Id + dir.Name = name //If there is a date add it if date != nil { @@ -175,7 +170,7 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil //If there is an error just return if err != nil { log.Printf("Unable to retrieve Drive client: %v\n", err) - return nil + return nil, err } //For each file @@ -185,33 +180,34 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil //If the item is a folder, get all of it's children if item.MimeType == "application/vnd.google-apps.folder" { //Get the child - childFolder := gog.BuildFileHierarchy(item.Id, buildPreview, includeFilter) + childFolder, err := gog.BuildListing(item.Id, previewLength, includeFilter) - //Now set the parent id to this + if err != nil { + return nil, err + } + + //Now set the parent Id to this childFolder.ParentId = dir.Id //Just add the child - dir.Items = append(dir.Items, childFolder) + dir.Listings = append(dir.Listings, *childFolder) } else if includeFilter(item.MimeType) { ////Else check the filter //Split the name and see if it is to be used name, date := gog.splitNameAndDate(item.Name) //Create a new document - doc := &Document{ - File: File{ - Id: item.Id, - Name: name, - }, - Type: item.MimeType, - + doc := file.Item{ + Id: item.Id, + Name: name, ParentId: dir.Id, + Type: item.MimeType, } + //Only build the previews if needed - if buildPreview { - doc.Preview = gog.GetFilePreview(item.Id) + if previewLength > 0 { + doc.Preview = gog.GetFilePreview(item.Id, previewLength) doc.ThumbnailUrl = gog.GetFileThumbnailUrl(item.Id) - } //If there is a date add it @@ -221,105 +217,17 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil //Store it dir.Items = append(dir.Items, doc) - } } } - return dir -} - -/** -Builds all of the forms and downloads them at the same time -*/ -func (gog *Drive) BuildFormHierarchy(dirId string) *Directory { - - //Get this item - folderInfo, err := gog.connection.Files. - Get(dirId). - SupportsTeamDrives(true). - Do() - - //Return nothing from this folder - if err != nil { - return nil - } - - //Get all of the files in this folder - dir := &Directory{ - File: File{ - Id: folderInfo.Id, - Name: folderInfo.Name, - }, - Type: folderInfo.MimeType, - Items: make([]Item, 0), - } - - //Now get all of the files - files, err := gog.connection.Files.List(). - SupportsTeamDrives(true). - IncludeTeamDriveItems(true). - Q("'" + dirId + "' in parents"). - Do() - - //If there is an error just return - if err != nil { - log.Printf("Unable to retrieve Drive client: %v\n", err) - return nil - } - - //For each file - for _, item := range files.Files { - //Make sure item is not trashed - if !item.Trashed { - //If the item is a folder, get all of it's children - if item.MimeType == "application/vnd.google-apps.folder" { - //Get the child - childFolder := gog.BuildFormHierarchy(item.Id) - - //Now set the parent id to this - childFolder.ParentId = dir.Id - - //Only add the form is there are any children - if len(childFolder.Items) > 0 { - - //Just add the child - dir.Items = append(dir.Items, childFolder) - } - - } else if item.MimeType == "application/json" { - //Now download the forms - form, err := gog.downloadForm(item.Id) - - //If there was an error - if err != nil { - log.Printf("Error: %v", err) - } else { - //Remove the extention - name := strings.TrimSuffix(item.Name, filepath.Ext(item.Name)) - - //Add the forms id - form.Id = item.Id - form.Name = name - form.ParentId = dir.Id - - //Now add it to the parents children - dir.Items = append(dir.Items, form) - - } - - } - } - - } - - return dir + return dir, nil } /** * Method to get the information hierarchy */ -func (gog *Drive) GetFilePreview(id string) string { +func (gog *Drive) GetFilePreview(id string, previewLength int) string { //Get the file type fileInfo, err := gog.connection.Files.Get(id).SupportsTeamDrives(true).Do() if err != nil { @@ -349,7 +257,6 @@ func (gog *Drive) GetFilePreview(id string) string { //Return only the first specified number of chars //Get the minimum value - previewLength := gog.previewLength if len(resultString) < previewLength { previewLength = len(resultString) } @@ -358,38 +265,6 @@ func (gog *Drive) GetFilePreview(id string) string { } -/** -* Method to get the information hierarchy - */ -func (gog *Drive) downloadForm(id string) (*Form, error) { - - //Get the plain text version of the file - resp, err := gog.connection.Files.Get(id).Download() - - //If there was an error just don't do anything - if err != nil { - return nil, err - } - - //Encode the response - dec := json.NewDecoder(resp.Body) - - //Createa a new forms - form := &Form{} - - //Now decode the stream into the forms - err = dec.Decode(form) - - //If there was an error just don't do anything - if err != nil { - return nil, err - } - - //Return it - return form, nil - -} - /** * Method to get the information hierarchy */ @@ -562,12 +437,11 @@ func (gog *Drive) GetMostRecentFileInDir(dirId string) (io.ReadCloser, error) { func (gog *Drive) GetFileAsInterface(id string, inter interface{}) error { //Get the resposne, rep, err := gog.GetArbitraryFile(id) - defer rep.Close() //If there was no error if err != nil { return err } - + defer rep.Close() //REad the data data, err := ioutil.ReadAll(rep) if err != nil { diff --git a/main.go b/main.go index 7ce8101..2f120b7 100644 --- a/main.go +++ b/main.go @@ -6,13 +6,14 @@ package main import ( "database/sql" "fmt" + "log" + "os" + "time" + _ "github.com/go-sql-driver/mysql" "github.com/reaction-eng/restlib/notification" "github.com/reaction-eng/restlib/stl" "github.com/reaction-eng/restlib/users" - "log" - "os" - "time" ) //Define the global variables that are setup in the main @@ -113,7 +114,7 @@ func main() { //config, _ := configuration.NewConfiguration("config.mysql.json", "config.host.json") localSql, err := sql.Open("mysql", "root:P1p3sh0p@tcp(:3306)/localDB?parseTime=true") //"root:P1p3sh0p@tcp(:3306)/localDB?parseTime=true" - sqlConnectiont := users.NewRepoMySql(localSql, "users") + sqlConnectiont, err := users.NewRepoMySql(localSql) //include some kind of db call to get email to who'm we send to userG, err := sqlConnectiont.GetUser(2) diff --git a/middleware/CORSHandler.go b/middleware/corsHandler.go similarity index 99% rename from middleware/CORSHandler.go rename to middleware/corsHandler.go index 21e864d..70a464f 100644 --- a/middleware/CORSHandler.go +++ b/middleware/corsHandler.go @@ -4,8 +4,9 @@ package middleware import ( - "github.com/gorilla/mux" "net/http" + + "github.com/gorilla/mux" ) /** diff --git a/middleware/corsHandler_test.go b/middleware/corsHandler_test.go new file mode 100644 index 0000000..5ff2569 --- /dev/null +++ b/middleware/corsHandler_test.go @@ -0,0 +1,40 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package middleware_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/reaction-eng/restlib/middleware" + + "github.com/stretchr/testify/assert" +) + +func TestMakeCORSMiddlewareFunc(t *testing.T) { + // arrange + r := httptest.NewRequest("GET", "http://localhost", nil) + w := httptest.NewRecorder() + + var wResponse http.ResponseWriter + var rResponse *http.Request + mockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + wResponse = w + rResponse = r + }) + + middleware := middleware.MakeCORSMiddlewareFunc() + handler := middleware.Middleware(mockHandler) + + // act + handler.ServeHTTP(w, r) + + // assert + assert.Equal(t, w, wResponse) + assert.Equal(t, r, rResponse) + assert.Equal(t, "*", w.Header().Get("Access-Control-Allow-Origin")) + assert.Equal(t, "Origin,authorization,content-type, x-ijt, X-Auth-Token, X-Requested-With", w.Header().Get("Access-Control-Allow-Headers")) + assert.Equal(t, "GET, POST, PATCH, PUT, DELETE, OPTIONS", w.Header().Get("Access-Control-Allow-Methods")) +} diff --git a/middleware/HttpsOnly.go b/middleware/httpsOnly.go similarity index 85% rename from middleware/HttpsOnly.go rename to middleware/httpsOnly.go index 804d720..edacdb4 100644 --- a/middleware/HttpsOnly.go +++ b/middleware/httpsOnly.go @@ -1,14 +1,11 @@ package middleware import ( - "github.com/gorilla/mux" "net/http" + + "github.com/gorilla/mux" ) -/** -Define a function to allow CORS -Allow CORS here By * or specific origin -*/ func HerokuHttpsOnlyMiddlewareFunc() mux.MiddlewareFunc { return func(next http.Handler) http.Handler { @@ -18,9 +15,7 @@ func HerokuHttpsOnlyMiddlewareFunc() mux.MiddlewareFunc { http.Redirect(w, r, sslUrl, http.StatusTemporaryRedirect) return } - next.ServeHTTP(w, r) - }) } } diff --git a/middleware/httpsOnly_test.go b/middleware/httpsOnly_test.go new file mode 100644 index 0000000..d35d45c --- /dev/null +++ b/middleware/httpsOnly_test.go @@ -0,0 +1,58 @@ +package middleware_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/reaction-eng/restlib/middleware" + "github.com/stretchr/testify/assert" +) + +func TestHerokuHttpsOnlyMiddlewareFunc(t *testing.T) { + testCases := []struct { + headerKey string + headerValue string + redirect bool + }{ + {"x-forwarded-proto", "https", false}, + {"x-forwarded-proto", "http", true}, + {"x-forwarded-proto", "something", true}, + {"other header", "https", true}, + {"*", "*", true}, + } + + for _, testCase := range testCases { + // arrange + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "http://localhost/example", nil) + + var wResponse http.ResponseWriter + var rResponse *http.Request + mockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + wResponse = w + rResponse = r + }) + + r.Header.Set(testCase.headerKey, testCase.headerValue) + + middleware := middleware.HerokuHttpsOnlyMiddlewareFunc() + handler := middleware.Middleware(mockHandler) + + // act + handler.ServeHTTP(w, r) + + // assert + if testCase.redirect { + assert.Equal(t, http.StatusTemporaryRedirect, w.Result().StatusCode) + assert.Equal(t, "Temporary Redirect.\n\n", w.Body.String()) + assert.Nil(t, wResponse) + assert.Nil(t, rResponse) + } else { + assert.Equal(t, w, wResponse) + assert.Equal(t, r, rResponse) + assert.Equal(t, http.StatusOK, w.Result().StatusCode) + assert.Empty(t, w.Body.String()) + } + } +} diff --git a/middleware/JwtHandler.go b/middleware/jwtHandler.go similarity index 75% rename from middleware/JwtHandler.go rename to middleware/jwtHandler.go index 462d875..ba52ee0 100644 --- a/middleware/JwtHandler.go +++ b/middleware/jwtHandler.go @@ -4,21 +4,23 @@ package middleware import ( + "errors" + "net/http" + "strings" + + "github.com/gorilla/mux" "github.com/reaction-eng/restlib/passwords" "github.com/reaction-eng/restlib/roles" "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/users" "github.com/reaction-eng/restlib/utils" - "github.com/gorilla/mux" "golang.org/x/net/context" - "net/http" - "strings" ) /** Define a function to handle checking for auth */ -func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo roles.Repo, passHelper passwords.Helper) mux.MiddlewareFunc { +func MakeJwtMiddlewareFunc(router routing.Router, userRepo users.Repo, permRepo roles.Repo, passHelper passwords.Helper) mux.MiddlewareFunc { //Return an instance return func(next http.Handler) http.Handler { @@ -42,6 +44,13 @@ func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo return } + //check if request does not need middleware, serve the request if it doesn't need it + if route.Public { + //Just serve it + next.ServeHTTP(w, r) + return + } + //tokenHeader will get set here if we have a websocket. If this isn't a websocket, tokenHeader will be "" //var tokenHeader string tokenHeader := r.Header.Get("Sec-Websocket-Protocol") @@ -49,12 +58,6 @@ func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo //if true, it's not a websocket. If it has something, we are dealing with a websocket. if tokenHeader == "" { - //check if request does not need middleware, serve the request if it doesn't need it - if route.Public { - //Just serve it - next.ServeHTTP(w, r) - return - } //Get the header for auth tokenHeader = r.Header.Get("Authorization") //Grab the token from the header } else { @@ -63,14 +66,18 @@ func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo tokenHeader = tokenHeader[0:locOfComma] } + if tokenHeader == "" { + utils.ReturnJsonError(w, http.StatusForbidden, errors.New("auth_missing_token")) + return + } + //Validate and get the user id - userId, tokenEmail, err := passHelper.ValidateToken(tokenHeader) + userId, orgId, tokenEmail, err := passHelper.ValidateToken(tokenHeader) //If there is an error return if err != nil { //Return the error utils.ReturnJsonError(w, http.StatusForbidden, err) - return } @@ -81,14 +88,13 @@ func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo if err != nil { //Return the error utils.ReturnJsonError(w, http.StatusForbidden, err) - return } + //Make sure the emails match in the token and logged in user - if loggedInUser.Email() != tokenEmail { + if loggedInUser == nil || loggedInUser.Email() != tokenEmail { //Return the error utils.ReturnJsonStatus(w, http.StatusForbidden, false, "auth_malformed_token") - return } @@ -96,12 +102,19 @@ func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo if !loggedInUser.Activated() { //There prob is not a user to return utils.ReturnJsonStatus(w, http.StatusForbidden, false, "user_not_activated") + return + } + + // Make sure that the user is in the org + if !users.InOrganization(loggedInUser, orgId) { + utils.ReturnJsonStatus(w, http.StatusForbidden, false, users.UserNotInOrganization.Error()) + return } //Make sure that the user has permission if permRepo != nil { //See if we are allowed - userPerm, err := permRepo.GetPermissions(loggedInUser) + userPerm, err := permRepo.GetPermissions(loggedInUser, orgId) //See if we are allowed to if err != nil || !userPerm.AllowedTo(route.ReqPermissions...) { @@ -114,7 +127,8 @@ func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo //Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token //fmt.Sprintf("User %", tk.Username) //Useful for monitoring - ctx := context.WithValue(r.Context(), "user", userId) + ctx := context.WithValue(r.Context(), utils.UserKey, userId) + ctx = context.WithValue(ctx, utils.OrganizationKey, orgId) r = r.WithContext(ctx) next.ServeHTTP(w, r) //proceed in the middleware chain! }) diff --git a/middleware/jwtHandler_test.go b/middleware/jwtHandler_test.go new file mode 100644 index 0000000..8cc2303 --- /dev/null +++ b/middleware/jwtHandler_test.go @@ -0,0 +1,454 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package middleware_test + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/reaction-eng/restlib/roles" + "github.com/reaction-eng/restlib/routing" + "github.com/reaction-eng/restlib/utils" + + "github.com/golang/mock/gomock" + + "github.com/reaction-eng/restlib/mocks" + + "github.com/reaction-eng/restlib/middleware" + "github.com/stretchr/testify/assert" +) + +func TestMakeJwtMiddlewareFunc(t *testing.T) { + testCases := []struct { + description string + method string + header map[string]string + setup func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) + status int + response string + next bool + context map[string]interface{} + }{ + { + "options", + "OPTIONS", + map[string]string{}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + + }, + http.StatusOK, + "", + true, + nil, + }, + { + "get without known route", + "GET", + map[string]string{}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(nil) + + }, + http.StatusForbidden, + "{\"message\":\"\",\"status\":false}\n", + false, + nil, + }, + { + "get without token", + "GET", + map[string]string{}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + }, + http.StatusForbidden, + "{\"message\":\"auth_missing_token\",\"status\":false}\n", + false, + nil, + }, + { + "get with public route", + "GET", + map[string]string{}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: true} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + }, + http.StatusOK, + "", + true, + nil, + }, + { + "get with public route with token", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: true} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + }, + http.StatusOK, + "", + true, + nil, + }, + { + "get with private route with bad token", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: false} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(0, 0, "", errors.New("bad token")) + + }, + http.StatusForbidden, + "{\"message\":\"bad token\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with bad websocket token", + "GET", + map[string]string{"Sec-Websocket-Protocol": "Example_Space_one two three,"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: false} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example one two three").Return(0, 0, "", errors.New("bad token")) + + }, + http.StatusForbidden, + "{\"message\":\"bad token\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token but no user", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: false} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "", nil) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(nil, nil) + + }, + http.StatusForbidden, + "{\"message\":\"auth_malformed_token\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token but user error", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: false} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "", nil) + + mockUser := mocks.NewMockUser(ctrl) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, errors.New("user error")) + + }, + http.StatusForbidden, + "{\"message\":\"user error\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token but valid user with wrong email", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: false} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("") + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + + }, + http.StatusForbidden, + "{\"message\":\"auth_malformed_token\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token but non activated user", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{Public: false} + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("example@example.com") + mockUser.EXPECT().Activated().Times(1).Return(false) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + + }, + http.StatusForbidden, + "{\"message\":\"user_not_activated\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token but without permission", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{ + Public: false, + ReqPermissions: []string{"req_permission"}, + } + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("example@example.com") + mockUser.EXPECT().Activated().Times(1).Return(true) + mockUser.EXPECT().Organizations().Times(1).Return([]int{1000}) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ + Permissions: []string{}, + }, nil) + }, + http.StatusForbidden, + "{\"message\":\"insufficient_access\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token but without permission", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{ + Public: false, + ReqPermissions: []string{"req_permission"}, + } + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("example@example.com") + mockUser.EXPECT().Activated().Times(1).Return(true) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + mockUser.EXPECT().Organizations().Times(1).Return([]int{1000}) + + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ + Permissions: []string{"req_permissio"}, + }, nil) + }, + http.StatusForbidden, + "{\"message\":\"insufficient_access\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token but with permission error", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{ + Public: false, + ReqPermissions: []string{}, + } + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("example@example.com") + mockUser.EXPECT().Activated().Times(1).Return(true) + mockUser.EXPECT().Organizations().Times(1).Return([]int{1000}) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ + Permissions: []string{}, + }, errors.New("permission error")) + }, + http.StatusForbidden, + "{\"message\":\"insufficient_access\",\"status\":false}\n", + false, + nil, + }, + { + "get with private route with good token wrong org", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{ + Public: false, + ReqPermissions: []string{}, + } + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("example@example.com") + mockUser.EXPECT().Activated().Times(1).Return(true) + mockUser.EXPECT().Organizations().Times(1).Return([]int{132}) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(0).Return(&roles.Permissions{ + Permissions: []string{}, + }, errors.New("user_not_in_organization")) + }, + http.StatusForbidden, + "{\"message\":\"user_not_in_organization\",\"status\":false}\n", + false, + nil, + }, + { + "get user context with private route without needed permissions", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{ + Public: false, + ReqPermissions: []string{}, + } + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("example@example.com") + mockUser.EXPECT().Activated().Times(1).Return(true) + mockUser.EXPECT().Organizations().Times(1).Return([]int{1000}) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ + Permissions: []string{}, + }, nil) + }, + http.StatusOK, + "", + true, + map[string]interface{}{utils.UserKey: 100, utils.OrganizationKey: 1000}, + }, + { + "get user context with private route with needed permissions", + "GET", + map[string]string{"Authorization": "Example Token"}, + func(ctrl *gomock.Controller, mockRouter *mocks.MockRouter, mockUsers *mocks.MockUserRepo, mockRoles *mocks.MockRolesRepo, mockHelper *mocks.MockHelper) { + route := &routing.Route{ + Public: false, + ReqPermissions: []string{"req_perm"}, + } + + mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) + + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) + + mockUser := mocks.NewMockUser(ctrl) + mockUser.EXPECT().Email().Times(1).Return("example@example.com") + mockUser.EXPECT().Activated().Times(1).Return(true) + mockUser.EXPECT().Organizations().Times(1).Return([]int{1000}) + + mockUsers.EXPECT().GetUser(100).Times(1).Return(mockUser, nil) + + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ + Permissions: []string{"perm 1", "perm 2", "req_perm", "perm 4"}, + }, nil) + }, + http.StatusOK, + "", + true, + map[string]interface{}{utils.UserKey: 100, utils.OrganizationKey: 1000}, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + w := httptest.NewRecorder() + r := httptest.NewRequest(testCase.method, "http://localhost/example", nil) + + var wResponse http.ResponseWriter + var rResponse *http.Request + mockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + wResponse = w + rResponse = r + }) + + for k, v := range testCase.header { + r.Header.Set(k, v) + } + + mockRouter := mocks.NewMockRouter(mockCtrl) + mockUsers := mocks.NewMockUserRepo(mockCtrl) + mockRoles := mocks.NewMockRolesRepo(mockCtrl) + mockHelper := mocks.NewMockHelper(mockCtrl) + + testCase.setup(mockCtrl, mockRouter, mockUsers, mockRoles, mockHelper) + + middleware := middleware.MakeJwtMiddlewareFunc(mockRouter, mockUsers, mockRoles, mockHelper) + handler := middleware.Middleware(mockHandler) + + // act + handler.ServeHTTP(w, r) + + // assert + if testCase.next { + for k, v := range testCase.context { + value := rResponse.Context().Value(k) + assert.Equal(t, v, value, testCase.description) + } + + assert.Equal(t, w, wResponse, testCase.description) + assert.Equal(t, http.StatusOK, w.Result().StatusCode, testCase.description) + assert.Empty(t, w.Body.String(), testCase.description) + } else { + assert.Equal(t, testCase.status, w.Result().StatusCode, testCase.description) + assert.Equal(t, testCase.response, w.Body.String(), testCase.description) + assert.Nil(t, wResponse, "should not proceed to the next handler", testCase.description) + assert.Nil(t, rResponse, "should not proceed to the next handler", testCase.description) + } + } +} diff --git a/migrations/migrations.go b/migrations/migrations.go new file mode 100644 index 0000000..c695a9e --- /dev/null +++ b/migrations/migrations.go @@ -0,0 +1,20 @@ +package migrations + +import ( + "github.com/gobuffalo/packr/v2" + migrate "github.com/rubenv/sql-migrate" +) + +type ReferenceType struct{} + +func MySql() migrate.MigrationSource { + return &migrate.PackrMigrationSource{ + Box: packr.New("mysql", "./mysql"), + } +} + +func Postgres() migrate.MigrationSource { + return &migrate.PackrMigrationSource{ + Box: packr.New("postgres", "/postgres"), + } +} diff --git a/migrations/mysql/1_initial_passwordReset.sql b/migrations/mysql/1_initial_passwordReset.sql new file mode 100644 index 0000000..7157ab4 --- /dev/null +++ b/migrations/mysql/1_initial_passwordReset.sql @@ -0,0 +1,6 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS resetrequests (id int NOT NULL AUTO_INCREMENT, userId int, email TEXT, token TEXT, issued DATE, type INT, PRIMARY KEY (id) ); + + +-- +migrate Down +DROP TABLE resetrequests; \ No newline at end of file diff --git a/migrations/mysql/2_initial_userPref.sql b/migrations/mysql/2_initial_userPref.sql new file mode 100644 index 0000000..9f3032e --- /dev/null +++ b/migrations/mysql/2_initial_userPref.sql @@ -0,0 +1,6 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS userpref (userId int NOT NULL, settings TEXT NOT NULL, PRIMARY KEY (userId) ); + + +-- +migrate Down +DROP TABLE userpref; \ No newline at end of file diff --git a/migrations/mysql/3_initial_users.sql b/migrations/mysql/3_initial_users.sql new file mode 100644 index 0000000..584b846 --- /dev/null +++ b/migrations/mysql/3_initial_users.sql @@ -0,0 +1,7 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS users (id int NOT NULL AUTO_INCREMENT, email TEXT, password TEXT, activation Date, PRIMARY KEY (id) ) +CREATE TABLE IF NOT EXISTS userOrganizations (userId int, orgId int, joinDate Date ) + +-- +migrate Down +DROP TABLE users; +DROP TABLE userpref; \ No newline at end of file diff --git a/migrations/mysql/4_initial_roles.sql b/migrations/mysql/4_initial_roles.sql new file mode 100644 index 0000000..0d7738b --- /dev/null +++ b/migrations/mysql/4_initial_roles.sql @@ -0,0 +1,5 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS roles (id int NOT NULL AUTO_INCREMENT, userId int, orgId int, roleId int, PRIMARY KEY (id) ) + +-- +migrate Down +DROP TABLE roles; diff --git a/migrations/postgres/1_initial_passwordReset.sql b/migrations/postgres/1_initial_passwordReset.sql new file mode 100644 index 0000000..e24bd97 --- /dev/null +++ b/migrations/postgres/1_initial_passwordReset.sql @@ -0,0 +1,5 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS resetrequests (id SERIAL PRIMARY KEY, userId int NOT NULL, email TEXT NOT NULL, token TEXT NOT NULL,issued DATE NOT NULL, type int NOT NULL); + +-- +migrate Down +DROP TABLE resetrequests; \ No newline at end of file diff --git a/migrations/postgres/2_initial_userPref.sql b/migrations/postgres/2_initial_userPref.sql new file mode 100644 index 0000000..7faa065 --- /dev/null +++ b/migrations/postgres/2_initial_userPref.sql @@ -0,0 +1,4 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS userpref (userId SERIAL PRIMARY KEY, settings TEXT NOT NULL); +-- +migrate Down +DROP TABLE userpref; \ No newline at end of file diff --git a/migrations/postgres/3_initial_users.sql b/migrations/postgres/3_initial_users.sql new file mode 100644 index 0000000..631249e --- /dev/null +++ b/migrations/postgres/3_initial_users.sql @@ -0,0 +1,9 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, email TEXT NOT NULL, password TEXT NOT NULL, activation Date); +CREATE TABLE IF NOT EXISTS userOrganizations (userId int NOT NULL, orgId int NOT NULL, joinDate Date); +-- +migrate StatementEnd + +-- +migrate Down +DROP TABLE users; +DROP TABLE userpref; +-- +migrate StatementEnd \ No newline at end of file diff --git a/migrations/postgres/4_initial_roles.sql b/migrations/postgres/4_initial_roles.sql new file mode 100644 index 0000000..28b34df --- /dev/null +++ b/migrations/postgres/4_initial_roles.sql @@ -0,0 +1,5 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS roles (id SERIAL PRIMARY KEY, userId int NOT NULL, orgId int NOT NULL, roleId int NOT NULL); + +-- +migrate Down +DROP TABLE roles; \ No newline at end of file diff --git a/mocks/mock_cache.go b/mocks/mock_cache.go new file mode 100644 index 0000000..de757a9 --- /dev/null +++ b/mocks/mock_cache.go @@ -0,0 +1,88 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/cache (interfaces: Cache) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockCache is a mock of Cache interface +type MockCache struct { + ctrl *gomock.Controller + recorder *MockCacheMockRecorder +} + +// MockCacheMockRecorder is the mock recorder for MockCache +type MockCacheMockRecorder struct { + mock *MockCache +} + +// NewMockCache creates a new mock instance +func NewMockCache(ctrl *gomock.Controller) *MockCache { + mock := &MockCache{ctrl: ctrl} + mock.recorder = &MockCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockCache) EXPECT() *MockCacheMockRecorder { + return m.recorder +} + +// Get mocks base method +func (m *MockCache) Get(arg0 string, arg1 interface{}) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Get indicates an expected call of Get +func (mr *MockCacheMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCache)(nil).Get), arg0, arg1) +} + +// GetString mocks base method +func (m *MockCache) GetString(arg0 string) (string, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetString", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetString indicates an expected call of GetString +func (mr *MockCacheMockRecorder) GetString(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockCache)(nil).GetString), arg0) +} + +// Set mocks base method +func (m *MockCache) Set(arg0 string, arg1 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Set", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Set indicates an expected call of Set +func (mr *MockCacheMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockCache)(nil).Set), arg0, arg1) +} + +// SetString mocks base method +func (m *MockCache) SetString(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetString", arg0, arg1) +} + +// SetString indicates an expected call of SetString +func (mr *MockCacheMockRecorder) SetString(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetString", reflect.TypeOf((*MockCache)(nil).SetString), arg0, arg1) +} diff --git a/mocks/mock_configuration.go b/mocks/mock_configuration.go new file mode 100644 index 0000000..3c4c230 --- /dev/null +++ b/mocks/mock_configuration.go @@ -0,0 +1,219 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/configuration (interfaces: Configuration) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + configuration "github.com/reaction-eng/restlib/configuration" + reflect "reflect" +) + +// MockConfiguration is a mock of Configuration interface +type MockConfiguration struct { + ctrl *gomock.Controller + recorder *MockConfigurationMockRecorder +} + +// MockConfigurationMockRecorder is the mock recorder for MockConfiguration +type MockConfigurationMockRecorder struct { + mock *MockConfiguration +} + +// NewMockConfiguration creates a new mock instance +func NewMockConfiguration(ctrl *gomock.Controller) *MockConfiguration { + mock := &MockConfiguration{ctrl: ctrl} + mock.recorder = &MockConfigurationMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockConfiguration) EXPECT() *MockConfigurationMockRecorder { + return m.recorder +} + +// Get mocks base method +func (m *MockConfiguration) Get(arg0 string) interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0) + ret0, _ := ret[0].(interface{}) + return ret0 +} + +// Get indicates an expected call of Get +func (mr *MockConfigurationMockRecorder) Get(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockConfiguration)(nil).Get), arg0) +} + +// GetBool mocks base method +func (m *MockConfiguration) GetBool(arg0 string, arg1 bool) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBool", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// GetBool indicates an expected call of GetBool +func (mr *MockConfigurationMockRecorder) GetBool(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockConfiguration)(nil).GetBool), arg0, arg1) +} + +// GetConfig mocks base method +func (m *MockConfiguration) GetConfig(arg0 string) configuration.Configuration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConfig", arg0) + ret0, _ := ret[0].(configuration.Configuration) + return ret0 +} + +// GetConfig indicates an expected call of GetConfig +func (mr *MockConfigurationMockRecorder) GetConfig(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", reflect.TypeOf((*MockConfiguration)(nil).GetConfig), arg0) +} + +// GetFatal mocks base method +func (m *MockConfiguration) GetFatal(arg0 string) interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFatal", arg0) + ret0, _ := ret[0].(interface{}) + return ret0 +} + +// GetFatal indicates an expected call of GetFatal +func (mr *MockConfigurationMockRecorder) GetFatal(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFatal", reflect.TypeOf((*MockConfiguration)(nil).GetFatal), arg0) +} + +// GetFloat mocks base method +func (m *MockConfiguration) GetFloat(arg0 string) (float64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFloat", arg0) + ret0, _ := ret[0].(float64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFloat indicates an expected call of GetFloat +func (mr *MockConfigurationMockRecorder) GetFloat(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFloat", reflect.TypeOf((*MockConfiguration)(nil).GetFloat), arg0) +} + +// GetInt mocks base method +func (m *MockConfiguration) GetInt(arg0 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInt", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInt indicates an expected call of GetInt +func (mr *MockConfigurationMockRecorder) GetInt(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockConfiguration)(nil).GetInt), arg0) +} + +// GetIntFatal mocks base method +func (m *MockConfiguration) GetIntFatal(arg0 string) int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetIntFatal", arg0) + ret0, _ := ret[0].(int) + return ret0 +} + +// GetIntFatal indicates an expected call of GetIntFatal +func (mr *MockConfigurationMockRecorder) GetIntFatal(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIntFatal", reflect.TypeOf((*MockConfiguration)(nil).GetIntFatal), arg0) +} + +// GetKeys mocks base method +func (m *MockConfiguration) GetKeys() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetKeys") + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetKeys indicates an expected call of GetKeys +func (mr *MockConfigurationMockRecorder) GetKeys() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeys", reflect.TypeOf((*MockConfiguration)(nil).GetKeys)) +} + +// GetString mocks base method +func (m *MockConfiguration) GetString(arg0 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetString", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetString indicates an expected call of GetString +func (mr *MockConfigurationMockRecorder) GetString(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockConfiguration)(nil).GetString), arg0) +} + +// GetStringArray mocks base method +func (m *MockConfiguration) GetStringArray(arg0 string) []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStringArray", arg0) + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetStringArray indicates an expected call of GetStringArray +func (mr *MockConfigurationMockRecorder) GetStringArray(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStringArray", reflect.TypeOf((*MockConfiguration)(nil).GetStringArray), arg0) +} + +// GetStringError mocks base method +func (m *MockConfiguration) GetStringError(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStringError", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetStringError indicates an expected call of GetStringError +func (mr *MockConfigurationMockRecorder) GetStringError(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStringError", reflect.TypeOf((*MockConfiguration)(nil).GetStringError), arg0) +} + +// GetStringFatal mocks base method +func (m *MockConfiguration) GetStringFatal(arg0 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStringFatal", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetStringFatal indicates an expected call of GetStringFatal +func (mr *MockConfigurationMockRecorder) GetStringFatal(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStringFatal", reflect.TypeOf((*MockConfiguration)(nil).GetStringFatal), arg0) +} + +// GetStruct mocks base method +func (m *MockConfiguration) GetStruct(arg0 string, arg1 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStruct", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetStruct indicates an expected call of GetStruct +func (mr *MockConfigurationMockRecorder) GetStruct(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStruct", reflect.TypeOf((*MockConfiguration)(nil).GetStruct), arg0, arg1) +} diff --git a/mocks/mock_emailer.go b/mocks/mock_emailer.go new file mode 100644 index 0000000..83cfd90 --- /dev/null +++ b/mocks/mock_emailer.go @@ -0,0 +1,170 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/email (interfaces: Emailer,TableInfo) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + email "github.com/reaction-eng/restlib/email" + utils "github.com/reaction-eng/restlib/utils" + reflect "reflect" +) + +// MockEmailer is a mock of Emailer interface +type MockEmailer struct { + ctrl *gomock.Controller + recorder *MockEmailerMockRecorder +} + +// MockEmailerMockRecorder is the mock recorder for MockEmailer +type MockEmailerMockRecorder struct { + mock *MockEmailer +} + +// NewMockEmailer creates a new mock instance +func NewMockEmailer(ctrl *gomock.Controller) *MockEmailer { + mock := &MockEmailer{ctrl: ctrl} + mock.recorder = &MockEmailerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockEmailer) EXPECT() *MockEmailerMockRecorder { + return m.recorder +} + +// Send mocks base method +func (m *MockEmailer) Send(arg0 *email.HeaderInfo, arg1 string, arg2 map[string][]*utils.Base64File) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Send indicates an expected call of Send +func (mr *MockEmailerMockRecorder) Send(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockEmailer)(nil).Send), arg0, arg1, arg2) +} + +// SendTable mocks base method +func (m *MockEmailer) SendTable(arg0 *email.HeaderInfo, arg1 email.TableInfo, arg2 map[string][]*utils.Base64File) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendTable", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendTable indicates an expected call of SendTable +func (mr *MockEmailerMockRecorder) SendTable(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTable", reflect.TypeOf((*MockEmailer)(nil).SendTable), arg0, arg1, arg2) +} + +// SendTemplateFile mocks base method +func (m *MockEmailer) SendTemplateFile(arg0 *email.HeaderInfo, arg1 string, arg2 interface{}, arg3 map[string][]*utils.Base64File) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendTemplateFile", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendTemplateFile indicates an expected call of SendTemplateFile +func (mr *MockEmailerMockRecorder) SendTemplateFile(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTemplateFile", reflect.TypeOf((*MockEmailer)(nil).SendTemplateFile), arg0, arg1, arg2, arg3) +} + +// SendTemplateString mocks base method +func (m *MockEmailer) SendTemplateString(arg0 *email.HeaderInfo, arg1 string, arg2 interface{}, arg3 map[string][]*utils.Base64File) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendTemplateString", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendTemplateString indicates an expected call of SendTemplateString +func (mr *MockEmailerMockRecorder) SendTemplateString(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTemplateString", reflect.TypeOf((*MockEmailer)(nil).SendTemplateString), arg0, arg1, arg2, arg3) +} + +// MockTableInfo is a mock of TableInfo interface +type MockTableInfo struct { + ctrl *gomock.Controller + recorder *MockTableInfoMockRecorder +} + +// MockTableInfoMockRecorder is the mock recorder for MockTableInfo +type MockTableInfoMockRecorder struct { + mock *MockTableInfo +} + +// NewMockTableInfo creates a new mock instance +func NewMockTableInfo(ctrl *gomock.Controller) *MockTableInfo { + mock := &MockTableInfo{ctrl: ctrl} + mock.recorder = &MockTableInfoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockTableInfo) EXPECT() *MockTableInfoMockRecorder { + return m.recorder +} + +// GetChildren mocks base method +func (m *MockTableInfo) GetChildren() []email.TableInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChildren") + ret0, _ := ret[0].([]email.TableInfo) + return ret0 +} + +// GetChildren indicates an expected call of GetChildren +func (mr *MockTableInfoMockRecorder) GetChildren() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChildren", reflect.TypeOf((*MockTableInfo)(nil).GetChildren)) +} + +// GetTitle mocks base method +func (m *MockTableInfo) GetTitle() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTitle") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetTitle indicates an expected call of GetTitle +func (mr *MockTableInfoMockRecorder) GetTitle() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTitle", reflect.TypeOf((*MockTableInfo)(nil).GetTitle)) +} + +// GetValue mocks base method +func (m *MockTableInfo) GetValue() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValue") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetValue indicates an expected call of GetValue +func (mr *MockTableInfoMockRecorder) GetValue() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockTableInfo)(nil).GetValue)) +} + +// IsNode mocks base method +func (m *MockTableInfo) IsNode() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsNode") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsNode indicates an expected call of IsNode +func (mr *MockTableInfoMockRecorder) IsNode() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNode", reflect.TypeOf((*MockTableInfo)(nil).IsNode)) +} diff --git a/mocks/mock_helper.go b/mocks/mock_helper.go new file mode 100644 index 0000000..c72951a --- /dev/null +++ b/mocks/mock_helper.go @@ -0,0 +1,120 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/passwords (interfaces: Helper) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockHelper is a mock of Helper interface +type MockHelper struct { + ctrl *gomock.Controller + recorder *MockHelperMockRecorder +} + +// MockHelperMockRecorder is the mock recorder for MockHelper +type MockHelperMockRecorder struct { + mock *MockHelper +} + +// NewMockHelper creates a new mock instance +func NewMockHelper(ctrl *gomock.Controller) *MockHelper { + mock := &MockHelper{ctrl: ctrl} + mock.recorder = &MockHelperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockHelper) EXPECT() *MockHelperMockRecorder { + return m.recorder +} + +// ComparePasswords mocks base method +func (m *MockHelper) ComparePasswords(arg0, arg1 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ComparePasswords", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// ComparePasswords indicates an expected call of ComparePasswords +func (mr *MockHelperMockRecorder) ComparePasswords(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ComparePasswords", reflect.TypeOf((*MockHelper)(nil).ComparePasswords), arg0, arg1) +} + +// CreateJWTToken mocks base method +func (m *MockHelper) CreateJWTToken(arg0, arg1 int, arg2 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateJWTToken", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + return ret0 +} + +// CreateJWTToken indicates an expected call of CreateJWTToken +func (mr *MockHelperMockRecorder) CreateJWTToken(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateJWTToken", reflect.TypeOf((*MockHelper)(nil).CreateJWTToken), arg0, arg1, arg2) +} + +// HashPassword mocks base method +func (m *MockHelper) HashPassword(arg0 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HashPassword", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// HashPassword indicates an expected call of HashPassword +func (mr *MockHelperMockRecorder) HashPassword(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashPassword", reflect.TypeOf((*MockHelper)(nil).HashPassword), arg0) +} + +// TokenGenerator mocks base method +func (m *MockHelper) TokenGenerator() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TokenGenerator") + ret0, _ := ret[0].(string) + return ret0 +} + +// TokenGenerator indicates an expected call of TokenGenerator +func (mr *MockHelperMockRecorder) TokenGenerator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TokenGenerator", reflect.TypeOf((*MockHelper)(nil).TokenGenerator)) +} + +// ValidatePassword mocks base method +func (m *MockHelper) ValidatePassword(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidatePassword", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidatePassword indicates an expected call of ValidatePassword +func (mr *MockHelperMockRecorder) ValidatePassword(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatePassword", reflect.TypeOf((*MockHelper)(nil).ValidatePassword), arg0) +} + +// ValidateToken mocks base method +func (m *MockHelper) ValidateToken(arg0 string) (int, int, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateToken", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(string) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// ValidateToken indicates an expected call of ValidateToken +func (mr *MockHelperMockRecorder) ValidateToken(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateToken", reflect.TypeOf((*MockHelper)(nil).ValidateToken), arg0) +} diff --git a/mocks/mock_memory.go b/mocks/mock_memory.go new file mode 100644 index 0000000..d5ccfd4 --- /dev/null +++ b/mocks/mock_memory.go @@ -0,0 +1,60 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/cache (interfaces: RawMemoryCache) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockRawMemoryCache is a mock of RawMemoryCache interface +type MockRawMemoryCache struct { + ctrl *gomock.Controller + recorder *MockRawMemoryCacheMockRecorder +} + +// MockRawMemoryCacheMockRecorder is the mock recorder for MockRawMemoryCache +type MockRawMemoryCacheMockRecorder struct { + mock *MockRawMemoryCache +} + +// NewMockRawMemoryCache creates a new mock instance +func NewMockRawMemoryCache(ctrl *gomock.Controller) *MockRawMemoryCache { + mock := &MockRawMemoryCache{ctrl: ctrl} + mock.recorder = &MockRawMemoryCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRawMemoryCache) EXPECT() *MockRawMemoryCacheMockRecorder { + return m.recorder +} + +// Get mocks base method +func (m *MockRawMemoryCache) Get(arg0 string) (interface{}, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// Get indicates an expected call of Get +func (mr *MockRawMemoryCacheMockRecorder) Get(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRawMemoryCache)(nil).Get), arg0) +} + +// SetDefault mocks base method +func (m *MockRawMemoryCache) SetDefault(arg0 string, arg1 interface{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetDefault", arg0, arg1) +} + +// SetDefault indicates an expected call of SetDefault +func (mr *MockRawMemoryCacheMockRecorder) SetDefault(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDefault", reflect.TypeOf((*MockRawMemoryCache)(nil).SetDefault), arg0, arg1) +} diff --git a/mocks/mock_permissionTable.go b/mocks/mock_permissionTable.go new file mode 100644 index 0000000..786008f --- /dev/null +++ b/mocks/mock_permissionTable.go @@ -0,0 +1,62 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/roles (interfaces: PermissionTable) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockPermissionTable is a mock of PermissionTable interface +type MockPermissionTable struct { + ctrl *gomock.Controller + recorder *MockPermissionTableMockRecorder +} + +// MockPermissionTableMockRecorder is the mock recorder for MockPermissionTable +type MockPermissionTableMockRecorder struct { + mock *MockPermissionTable +} + +// NewMockPermissionTable creates a new mock instance +func NewMockPermissionTable(ctrl *gomock.Controller) *MockPermissionTable { + mock := &MockPermissionTable{ctrl: ctrl} + mock.recorder = &MockPermissionTableMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockPermissionTable) EXPECT() *MockPermissionTableMockRecorder { + return m.recorder +} + +// GetPermissions mocks base method +func (m *MockPermissionTable) GetPermissions(arg0 int) []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPermissions", arg0) + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetPermissions indicates an expected call of GetPermissions +func (mr *MockPermissionTableMockRecorder) GetPermissions(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPermissions", reflect.TypeOf((*MockPermissionTable)(nil).GetPermissions), arg0) +} + +// LookUpRoleId mocks base method +func (m *MockPermissionTable) LookUpRoleId(arg0 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LookUpRoleId", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LookUpRoleId indicates an expected call of LookUpRoleId +func (mr *MockPermissionTableMockRecorder) LookUpRoleId(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookUpRoleId", reflect.TypeOf((*MockPermissionTable)(nil).LookUpRoleId), arg0) +} diff --git a/mocks/mock_preferencesRepo.go b/mocks/mock_preferencesRepo.go new file mode 100644 index 0000000..98fe974 --- /dev/null +++ b/mocks/mock_preferencesRepo.go @@ -0,0 +1,77 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/preferences (interfaces: Repo) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + preferences "github.com/reaction-eng/restlib/preferences" + users "github.com/reaction-eng/restlib/users" + reflect "reflect" +) + +// MockPreferencesRepo is a mock of Repo interface +type MockPreferencesRepo struct { + ctrl *gomock.Controller + recorder *MockPreferencesRepoMockRecorder +} + +// MockPreferencesRepoMockRecorder is the mock recorder for MockPreferencesRepo +type MockPreferencesRepoMockRecorder struct { + mock *MockPreferencesRepo +} + +// NewMockPreferencesRepo creates a new mock instance +func NewMockPreferencesRepo(ctrl *gomock.Controller) *MockPreferencesRepo { + mock := &MockPreferencesRepo{ctrl: ctrl} + mock.recorder = &MockPreferencesRepoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockPreferencesRepo) EXPECT() *MockPreferencesRepoMockRecorder { + return m.recorder +} + +// CleanUp mocks base method +func (m *MockPreferencesRepo) CleanUp() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CleanUp") +} + +// CleanUp indicates an expected call of CleanUp +func (mr *MockPreferencesRepoMockRecorder) CleanUp() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUp", reflect.TypeOf((*MockPreferencesRepo)(nil).CleanUp)) +} + +// GetPreferences mocks base method +func (m *MockPreferencesRepo) GetPreferences(arg0 users.User) (*preferences.Preferences, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPreferences", arg0) + ret0, _ := ret[0].(*preferences.Preferences) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPreferences indicates an expected call of GetPreferences +func (mr *MockPreferencesRepoMockRecorder) GetPreferences(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPreferences", reflect.TypeOf((*MockPreferencesRepo)(nil).GetPreferences), arg0) +} + +// SetPreferences mocks base method +func (m *MockPreferencesRepo) SetPreferences(arg0 users.User, arg1 *preferences.SettingGroup) (*preferences.Preferences, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetPreferences", arg0, arg1) + ret0, _ := ret[0].(*preferences.Preferences) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetPreferences indicates an expected call of SetPreferences +func (mr *MockPreferencesRepoMockRecorder) SetPreferences(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPreferences", reflect.TypeOf((*MockPreferencesRepo)(nil).SetPreferences), arg0, arg1) +} diff --git a/mocks/mock_resetRepo.go b/mocks/mock_resetRepo.go new file mode 100644 index 0000000..a3dbfa0 --- /dev/null +++ b/mocks/mock_resetRepo.go @@ -0,0 +1,146 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/passwords (interfaces: ResetRepo) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockResetRepo is a mock of ResetRepo interface +type MockResetRepo struct { + ctrl *gomock.Controller + recorder *MockResetRepoMockRecorder +} + +// MockResetRepoMockRecorder is the mock recorder for MockResetRepo +type MockResetRepoMockRecorder struct { + mock *MockResetRepo +} + +// NewMockResetRepo creates a new mock instance +func NewMockResetRepo(ctrl *gomock.Controller) *MockResetRepo { + mock := &MockResetRepo{ctrl: ctrl} + mock.recorder = &MockResetRepoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockResetRepo) EXPECT() *MockResetRepoMockRecorder { + return m.recorder +} + +// CheckForActivationToken mocks base method +func (m *MockResetRepo) CheckForActivationToken(arg0 int, arg1 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckForActivationToken", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckForActivationToken indicates an expected call of CheckForActivationToken +func (mr *MockResetRepoMockRecorder) CheckForActivationToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckForActivationToken", reflect.TypeOf((*MockResetRepo)(nil).CheckForActivationToken), arg0, arg1) +} + +// CheckForOneTimePasswordToken mocks base method +func (m *MockResetRepo) CheckForOneTimePasswordToken(arg0 int, arg1 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckForOneTimePasswordToken", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckForOneTimePasswordToken indicates an expected call of CheckForOneTimePasswordToken +func (mr *MockResetRepoMockRecorder) CheckForOneTimePasswordToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckForOneTimePasswordToken", reflect.TypeOf((*MockResetRepo)(nil).CheckForOneTimePasswordToken), arg0, arg1) +} + +// CheckForResetToken mocks base method +func (m *MockResetRepo) CheckForResetToken(arg0 int, arg1 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckForResetToken", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckForResetToken indicates an expected call of CheckForResetToken +func (mr *MockResetRepoMockRecorder) CheckForResetToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckForResetToken", reflect.TypeOf((*MockResetRepo)(nil).CheckForResetToken), arg0, arg1) +} + +// CleanUp mocks base method +func (m *MockResetRepo) CleanUp() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CleanUp") +} + +// CleanUp indicates an expected call of CleanUp +func (mr *MockResetRepoMockRecorder) CleanUp() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUp", reflect.TypeOf((*MockResetRepo)(nil).CleanUp)) +} + +// IssueActivationRequest mocks base method +func (m *MockResetRepo) IssueActivationRequest(arg0 string, arg1 int, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IssueActivationRequest", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// IssueActivationRequest indicates an expected call of IssueActivationRequest +func (mr *MockResetRepoMockRecorder) IssueActivationRequest(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueActivationRequest", reflect.TypeOf((*MockResetRepo)(nil).IssueActivationRequest), arg0, arg1, arg2) +} + +// IssueOneTimePasswordRequest mocks base method +func (m *MockResetRepo) IssueOneTimePasswordRequest(arg0 string, arg1 int, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IssueOneTimePasswordRequest", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// IssueOneTimePasswordRequest indicates an expected call of IssueOneTimePasswordRequest +func (mr *MockResetRepoMockRecorder) IssueOneTimePasswordRequest(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueOneTimePasswordRequest", reflect.TypeOf((*MockResetRepo)(nil).IssueOneTimePasswordRequest), arg0, arg1, arg2) +} + +// IssueResetRequest mocks base method +func (m *MockResetRepo) IssueResetRequest(arg0 string, arg1 int, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IssueResetRequest", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// IssueResetRequest indicates an expected call of IssueResetRequest +func (mr *MockResetRepoMockRecorder) IssueResetRequest(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueResetRequest", reflect.TypeOf((*MockResetRepo)(nil).IssueResetRequest), arg0, arg1, arg2) +} + +// UseToken mocks base method +func (m *MockResetRepo) UseToken(arg0 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseToken", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// UseToken indicates an expected call of UseToken +func (mr *MockResetRepoMockRecorder) UseToken(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseToken", reflect.TypeOf((*MockResetRepo)(nil).UseToken), arg0) +} diff --git a/mocks/mock_roles_repo.go b/mocks/mock_roles_repo.go new file mode 100644 index 0000000..214ef68 --- /dev/null +++ b/mocks/mock_roles_repo.go @@ -0,0 +1,78 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/roles (interfaces: Repo) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + roles "github.com/reaction-eng/restlib/roles" + users "github.com/reaction-eng/restlib/users" + reflect "reflect" +) + +// MockRolesRepo is a mock of Repo interface +type MockRolesRepo struct { + ctrl *gomock.Controller + recorder *MockRolesRepoMockRecorder +} + +// MockRolesRepoMockRecorder is the mock recorder for MockRolesRepo +type MockRolesRepoMockRecorder struct { + mock *MockRolesRepo +} + +// NewMockRolesRepo creates a new mock instance +func NewMockRolesRepo(ctrl *gomock.Controller) *MockRolesRepo { + mock := &MockRolesRepo{ctrl: ctrl} + mock.recorder = &MockRolesRepoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRolesRepo) EXPECT() *MockRolesRepoMockRecorder { + return m.recorder +} + +// GetPermissions mocks base method +func (m *MockRolesRepo) GetPermissions(arg0 users.User, arg1 int) (*roles.Permissions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPermissions", arg0, arg1) + ret0, _ := ret[0].(*roles.Permissions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPermissions indicates an expected call of GetPermissions +func (mr *MockRolesRepoMockRecorder) GetPermissions(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPermissions", reflect.TypeOf((*MockRolesRepo)(nil).GetPermissions), arg0, arg1) +} + +// SetRolesByName mocks base method +func (m *MockRolesRepo) SetRolesByName(arg0 users.User, arg1 int, arg2 []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRolesByName", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRolesByName indicates an expected call of SetRolesByName +func (mr *MockRolesRepoMockRecorder) SetRolesByName(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRolesByName", reflect.TypeOf((*MockRolesRepo)(nil).SetRolesByName), arg0, arg1, arg2) +} + +// SetRolesByRoleId mocks base method +func (m *MockRolesRepo) SetRolesByRoleId(arg0 users.User, arg1 int, arg2 []int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRolesByRoleId", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRolesByRoleId indicates an expected call of SetRolesByRoleId +func (mr *MockRolesRepoMockRecorder) SetRolesByRoleId(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRolesByRoleId", reflect.TypeOf((*MockRolesRepo)(nil).SetRolesByRoleId), arg0, arg1, arg2) +} diff --git a/mocks/mock_router.go b/mocks/mock_router.go new file mode 100644 index 0000000..969eb3c --- /dev/null +++ b/mocks/mock_router.go @@ -0,0 +1,49 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/routing (interfaces: Router) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + routing "github.com/reaction-eng/restlib/routing" + http "net/http" + reflect "reflect" +) + +// MockRouter is a mock of Router interface +type MockRouter struct { + ctrl *gomock.Controller + recorder *MockRouterMockRecorder +} + +// MockRouterMockRecorder is the mock recorder for MockRouter +type MockRouterMockRecorder struct { + mock *MockRouter +} + +// NewMockRouter creates a new mock instance +func NewMockRouter(ctrl *gomock.Controller) *MockRouter { + mock := &MockRouter{ctrl: ctrl} + mock.recorder = &MockRouterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRouter) EXPECT() *MockRouterMockRecorder { + return m.recorder +} + +// GetRoute mocks base method +func (m *MockRouter) GetRoute(arg0 *http.Request) *routing.Route { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoute", arg0) + ret0, _ := ret[0].(*routing.Route) + return ret0 +} + +// GetRoute indicates an expected call of GetRoute +func (mr *MockRouterMockRecorder) GetRoute(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoute", reflect.TypeOf((*MockRouter)(nil).GetRoute), arg0) +} diff --git a/mocks/mock_smtpConnection.go b/mocks/mock_smtpConnection.go new file mode 100644 index 0000000..08cac4a --- /dev/null +++ b/mocks/mock_smtpConnection.go @@ -0,0 +1,192 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/email (interfaces: SmtpConnection,Mail) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + email "github.com/reaction-eng/restlib/email" + io "io" + reflect "reflect" +) + +// MockSmtpConnection is a mock of SmtpConnection interface +type MockSmtpConnection struct { + ctrl *gomock.Controller + recorder *MockSmtpConnectionMockRecorder +} + +// MockSmtpConnectionMockRecorder is the mock recorder for MockSmtpConnection +type MockSmtpConnectionMockRecorder struct { + mock *MockSmtpConnection +} + +// NewMockSmtpConnection creates a new mock instance +func NewMockSmtpConnection(ctrl *gomock.Controller) *MockSmtpConnection { + mock := &MockSmtpConnection{ctrl: ctrl} + mock.recorder = &MockSmtpConnectionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockSmtpConnection) EXPECT() *MockSmtpConnectionMockRecorder { + return m.recorder +} + +// New mocks base method +func (m *MockSmtpConnection) New(arg0, arg1, arg2, arg3, arg4 string) email.Mail { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "New", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(email.Mail) + return ret0 +} + +// New indicates an expected call of New +func (mr *MockSmtpConnectionMockRecorder) New(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockSmtpConnection)(nil).New), arg0, arg1, arg2, arg3, arg4) +} + +// MockMail is a mock of Mail interface +type MockMail struct { + ctrl *gomock.Controller + recorder *MockMailMockRecorder +} + +// MockMailMockRecorder is the mock recorder for MockMail +type MockMailMockRecorder struct { + mock *MockMail +} + +// NewMockMail creates a new mock instance +func NewMockMail(ctrl *gomock.Controller) *MockMail { + mock := &MockMail{ctrl: ctrl} + mock.recorder = &MockMailMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockMail) EXPECT() *MockMailMockRecorder { + return m.recorder +} + +// Attach mocks base method +func (m *MockMail) Attach(arg0 string, arg1 io.Reader) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Attach", arg0, arg1) +} + +// Attach indicates an expected call of Attach +func (mr *MockMailMockRecorder) Attach(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Attach", reflect.TypeOf((*MockMail)(nil).Attach), arg0, arg1) +} + +// Bcc mocks base method +func (m *MockMail) Bcc(arg0 ...string) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Bcc", varargs...) +} + +// Bcc indicates an expected call of Bcc +func (mr *MockMailMockRecorder) Bcc(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bcc", reflect.TypeOf((*MockMail)(nil).Bcc), arg0...) +} + +// From mocks base method +func (m *MockMail) From(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "From", arg0) +} + +// From indicates an expected call of From +func (mr *MockMailMockRecorder) From(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "From", reflect.TypeOf((*MockMail)(nil).From), arg0) +} + +// Html mocks base method +func (m *MockMail) Html() io.Writer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Html") + ret0, _ := ret[0].(io.Writer) + return ret0 +} + +// Html indicates an expected call of Html +func (mr *MockMailMockRecorder) Html() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Html", reflect.TypeOf((*MockMail)(nil).Html)) +} + +// ReplyTo mocks base method +func (m *MockMail) ReplyTo(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ReplyTo", arg0) +} + +// ReplyTo indicates an expected call of ReplyTo +func (mr *MockMailMockRecorder) ReplyTo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplyTo", reflect.TypeOf((*MockMail)(nil).ReplyTo), arg0) +} + +// Send mocks base method +func (m *MockMail) Send() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send") + ret0, _ := ret[0].(error) + return ret0 +} + +// Send indicates an expected call of Send +func (mr *MockMailMockRecorder) Send() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockMail)(nil).Send)) +} + +// SetPlain mocks base method +func (m *MockMail) SetPlain(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetPlain", arg0) +} + +// SetPlain indicates an expected call of SetPlain +func (mr *MockMailMockRecorder) SetPlain(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPlain", reflect.TypeOf((*MockMail)(nil).SetPlain), arg0) +} + +// Subject mocks base method +func (m *MockMail) Subject(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Subject", arg0) +} + +// Subject indicates an expected call of Subject +func (mr *MockMailMockRecorder) Subject(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subject", reflect.TypeOf((*MockMail)(nil).Subject), arg0) +} + +// To mocks base method +func (m *MockMail) To(arg0 ...string) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "To", varargs...) +} + +// To indicates an expected call of To +func (mr *MockMailMockRecorder) To(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "To", reflect.TypeOf((*MockMail)(nil).To), arg0...) +} diff --git a/mocks/mock_storage.go b/mocks/mock_storage.go new file mode 100644 index 0000000..0dd2a59 --- /dev/null +++ b/mocks/mock_storage.go @@ -0,0 +1,166 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/file (interfaces: Storage) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + file "github.com/reaction-eng/restlib/file" + io "io" + reflect "reflect" +) + +// MockStorage is a mock of Storage interface +type MockStorage struct { + ctrl *gomock.Controller + recorder *MockStorageMockRecorder +} + +// MockStorageMockRecorder is the mock recorder for MockStorage +type MockStorageMockRecorder struct { + mock *MockStorage +} + +// NewMockStorage creates a new mock instance +func NewMockStorage(ctrl *gomock.Controller) *MockStorage { + mock := &MockStorage{ctrl: ctrl} + mock.recorder = &MockStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockStorage) EXPECT() *MockStorageMockRecorder { + return m.recorder +} + +// BuildListing mocks base method +func (m *MockStorage) BuildListing(arg0 string, arg1 int, arg2 func(string) bool) (*file.Listing, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildListing", arg0, arg1, arg2) + ret0, _ := ret[0].(*file.Listing) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildListing indicates an expected call of BuildListing +func (mr *MockStorageMockRecorder) BuildListing(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildListing", reflect.TypeOf((*MockStorage)(nil).BuildListing), arg0, arg1, arg2) +} + +// GetArbitraryFile mocks base method +func (m *MockStorage) GetArbitraryFile(arg0 string) (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetArbitraryFile", arg0) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetArbitraryFile indicates an expected call of GetArbitraryFile +func (mr *MockStorageMockRecorder) GetArbitraryFile(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetArbitraryFile", reflect.TypeOf((*MockStorage)(nil).GetArbitraryFile), arg0) +} + +// GetFileAsInterface mocks base method +func (m *MockStorage) GetFileAsInterface(arg0 string, arg1 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFileAsInterface", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetFileAsInterface indicates an expected call of GetFileAsInterface +func (mr *MockStorageMockRecorder) GetFileAsInterface(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileAsInterface", reflect.TypeOf((*MockStorage)(nil).GetFileAsInterface), arg0, arg1) +} + +// GetFileHtml mocks base method +func (m *MockStorage) GetFileHtml(arg0 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFileHtml", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetFileHtml indicates an expected call of GetFileHtml +func (mr *MockStorageMockRecorder) GetFileHtml(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileHtml", reflect.TypeOf((*MockStorage)(nil).GetFileHtml), arg0) +} + +// GetFilePreview mocks base method +func (m *MockStorage) GetFilePreview(arg0 string, arg1 int) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilePreview", arg0, arg1) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetFilePreview indicates an expected call of GetFilePreview +func (mr *MockStorageMockRecorder) GetFilePreview(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilePreview", reflect.TypeOf((*MockStorage)(nil).GetFilePreview), arg0, arg1) +} + +// GetFileThumbnailUrl mocks base method +func (m *MockStorage) GetFileThumbnailUrl(arg0 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFileThumbnailUrl", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetFileThumbnailUrl indicates an expected call of GetFileThumbnailUrl +func (mr *MockStorageMockRecorder) GetFileThumbnailUrl(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileThumbnailUrl", reflect.TypeOf((*MockStorage)(nil).GetFileThumbnailUrl), arg0) +} + +// GetFirstFileMatching mocks base method +func (m *MockStorage) GetFirstFileMatching(arg0, arg1 string) (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFirstFileMatching", arg0, arg1) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFirstFileMatching indicates an expected call of GetFirstFileMatching +func (mr *MockStorageMockRecorder) GetFirstFileMatching(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFirstFileMatching", reflect.TypeOf((*MockStorage)(nil).GetFirstFileMatching), arg0, arg1) +} + +// GetMostRecentFileInDir mocks base method +func (m *MockStorage) GetMostRecentFileInDir(arg0 string) (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMostRecentFileInDir", arg0) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMostRecentFileInDir indicates an expected call of GetMostRecentFileInDir +func (mr *MockStorageMockRecorder) GetMostRecentFileInDir(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMostRecentFileInDir", reflect.TypeOf((*MockStorage)(nil).GetMostRecentFileInDir), arg0) +} + +// PostArbitraryFile mocks base method +func (m *MockStorage) PostArbitraryFile(arg0, arg1 string, arg2 io.Reader, arg3 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PostArbitraryFile", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PostArbitraryFile indicates an expected call of PostArbitraryFile +func (mr *MockStorageMockRecorder) PostArbitraryFile(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostArbitraryFile", reflect.TypeOf((*MockStorage)(nil).PostArbitraryFile), arg0, arg1, arg2, arg3) +} diff --git a/mocks/mock_user.go b/mocks/mock_user.go new file mode 100644 index 0000000..4d40def --- /dev/null +++ b/mocks/mock_user.go @@ -0,0 +1,195 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/users (interfaces: User) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockUser is a mock of User interface +type MockUser struct { + ctrl *gomock.Controller + recorder *MockUserMockRecorder +} + +// MockUserMockRecorder is the mock recorder for MockUser +type MockUserMockRecorder struct { + mock *MockUser +} + +// NewMockUser creates a new mock instance +func NewMockUser(ctrl *gomock.Controller) *MockUser { + mock := &MockUser{ctrl: ctrl} + mock.recorder = &MockUserMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockUser) EXPECT() *MockUserMockRecorder { + return m.recorder +} + +// Activated mocks base method +func (m *MockUser) Activated() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Activated") + ret0, _ := ret[0].(bool) + return ret0 +} + +// Activated indicates an expected call of Activated +func (mr *MockUserMockRecorder) Activated() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Activated", reflect.TypeOf((*MockUser)(nil).Activated)) +} + +// Email mocks base method +func (m *MockUser) Email() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Email") + ret0, _ := ret[0].(string) + return ret0 +} + +// Email indicates an expected call of Email +func (mr *MockUserMockRecorder) Email() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Email", reflect.TypeOf((*MockUser)(nil).Email)) +} + +// Id mocks base method +func (m *MockUser) Id() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Id") + ret0, _ := ret[0].(int) + return ret0 +} + +// Id indicates an expected call of Id +func (mr *MockUserMockRecorder) Id() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockUser)(nil).Id)) +} + +// Organizations mocks base method +func (m *MockUser) Organizations() []int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Organizations") + ret0, _ := ret[0].([]int) + return ret0 +} + +// Organizations indicates an expected call of Organizations +func (mr *MockUserMockRecorder) Organizations() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Organizations", reflect.TypeOf((*MockUser)(nil).Organizations)) +} + +// Password mocks base method +func (m *MockUser) Password() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Password") + ret0, _ := ret[0].(string) + return ret0 +} + +// Password indicates an expected call of Password +func (mr *MockUserMockRecorder) Password() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Password", reflect.TypeOf((*MockUser)(nil).Password)) +} + +// PasswordLogin mocks base method +func (m *MockUser) PasswordLogin() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PasswordLogin") + ret0, _ := ret[0].(bool) + return ret0 +} + +// PasswordLogin indicates an expected call of PasswordLogin +func (mr *MockUserMockRecorder) PasswordLogin() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordLogin", reflect.TypeOf((*MockUser)(nil).PasswordLogin)) +} + +// SetEmail mocks base method +func (m *MockUser) SetEmail(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetEmail", arg0) +} + +// SetEmail indicates an expected call of SetEmail +func (mr *MockUserMockRecorder) SetEmail(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEmail", reflect.TypeOf((*MockUser)(nil).SetEmail), arg0) +} + +// SetId mocks base method +func (m *MockUser) SetId(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetId", arg0) +} + +// SetId indicates an expected call of SetId +func (mr *MockUserMockRecorder) SetId(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetId", reflect.TypeOf((*MockUser)(nil).SetId), arg0) +} + +// SetOrganizations mocks base method +func (m *MockUser) SetOrganizations(arg0 ...int) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "SetOrganizations", varargs...) +} + +// SetOrganizations indicates an expected call of SetOrganizations +func (mr *MockUserMockRecorder) SetOrganizations(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetOrganizations", reflect.TypeOf((*MockUser)(nil).SetOrganizations), arg0...) +} + +// SetPassword mocks base method +func (m *MockUser) SetPassword(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetPassword", arg0) +} + +// SetPassword indicates an expected call of SetPassword +func (mr *MockUserMockRecorder) SetPassword(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPassword", reflect.TypeOf((*MockUser)(nil).SetPassword), arg0) +} + +// SetToken mocks base method +func (m *MockUser) SetToken(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetToken", arg0) +} + +// SetToken indicates an expected call of SetToken +func (mr *MockUserMockRecorder) SetToken(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetToken", reflect.TypeOf((*MockUser)(nil).SetToken), arg0) +} + +// Token mocks base method +func (m *MockUser) Token() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Token") + ret0, _ := ret[0].(string) + return ret0 +} + +// Token indicates an expected call of Token +func (mr *MockUserMockRecorder) Token() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Token", reflect.TypeOf((*MockUser)(nil).Token)) +} diff --git a/mocks/mock_user_helper.go b/mocks/mock_user_helper.go new file mode 100644 index 0000000..ba8a0c2 --- /dev/null +++ b/mocks/mock_user_helper.go @@ -0,0 +1,437 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/users (interfaces: Helper) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + users "github.com/reaction-eng/restlib/users" + reflect "reflect" +) + +// MockUserHelper is a mock of Helper interface +type MockUserHelper struct { + ctrl *gomock.Controller + recorder *MockUserHelperMockRecorder +} + +// MockUserHelperMockRecorder is the mock recorder for MockUserHelper +type MockUserHelperMockRecorder struct { + mock *MockUserHelper +} + +// NewMockUserHelper creates a new mock instance +func NewMockUserHelper(ctrl *gomock.Controller) *MockUserHelper { + mock := &MockUserHelper{ctrl: ctrl} + mock.recorder = &MockUserHelperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockUserHelper) EXPECT() *MockUserHelperMockRecorder { + return m.recorder +} + +// ActivateUser mocks base method +func (m *MockUserHelper) ActivateUser(arg0 users.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ActivateUser", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ActivateUser indicates an expected call of ActivateUser +func (mr *MockUserHelperMockRecorder) ActivateUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActivateUser", reflect.TypeOf((*MockUserHelper)(nil).ActivateUser), arg0) +} + +// AddUser mocks base method +func (m *MockUserHelper) AddUser(arg0 users.User) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddUser", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddUser indicates an expected call of AddUser +func (mr *MockUserHelperMockRecorder) AddUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUser", reflect.TypeOf((*MockUserHelper)(nil).AddUser), arg0) +} + +// AddUserToOrganization mocks base method +func (m *MockUserHelper) AddUserToOrganization(arg0 users.User, arg1 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddUserToOrganization", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddUserToOrganization indicates an expected call of AddUserToOrganization +func (mr *MockUserHelperMockRecorder) AddUserToOrganization(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUserToOrganization", reflect.TypeOf((*MockUserHelper)(nil).AddUserToOrganization), arg0, arg1) +} + +// CheckForActivationToken mocks base method +func (m *MockUserHelper) CheckForActivationToken(arg0 int, arg1 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckForActivationToken", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckForActivationToken indicates an expected call of CheckForActivationToken +func (mr *MockUserHelperMockRecorder) CheckForActivationToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckForActivationToken", reflect.TypeOf((*MockUserHelper)(nil).CheckForActivationToken), arg0, arg1) +} + +// CheckForOneTimePasswordToken mocks base method +func (m *MockUserHelper) CheckForOneTimePasswordToken(arg0 int, arg1 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckForOneTimePasswordToken", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckForOneTimePasswordToken indicates an expected call of CheckForOneTimePasswordToken +func (mr *MockUserHelperMockRecorder) CheckForOneTimePasswordToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckForOneTimePasswordToken", reflect.TypeOf((*MockUserHelper)(nil).CheckForOneTimePasswordToken), arg0, arg1) +} + +// CheckForResetToken mocks base method +func (m *MockUserHelper) CheckForResetToken(arg0 int, arg1 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckForResetToken", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckForResetToken indicates an expected call of CheckForResetToken +func (mr *MockUserHelperMockRecorder) CheckForResetToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckForResetToken", reflect.TypeOf((*MockUserHelper)(nil).CheckForResetToken), arg0, arg1) +} + +// CleanUp mocks base method +func (m *MockUserHelper) CleanUp() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CleanUp") +} + +// CleanUp indicates an expected call of CleanUp +func (mr *MockUserHelperMockRecorder) CleanUp() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUp", reflect.TypeOf((*MockUserHelper)(nil).CleanUp)) +} + +// ComparePasswords mocks base method +func (m *MockUserHelper) ComparePasswords(arg0, arg1 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ComparePasswords", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// ComparePasswords indicates an expected call of ComparePasswords +func (mr *MockUserHelperMockRecorder) ComparePasswords(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ComparePasswords", reflect.TypeOf((*MockUserHelper)(nil).ComparePasswords), arg0, arg1) +} + +// CreateJWTToken mocks base method +func (m *MockUserHelper) CreateJWTToken(arg0, arg1 int, arg2 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateJWTToken", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + return ret0 +} + +// CreateJWTToken indicates an expected call of CreateJWTToken +func (mr *MockUserHelperMockRecorder) CreateJWTToken(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateJWTToken", reflect.TypeOf((*MockUserHelper)(nil).CreateJWTToken), arg0, arg1, arg2) +} + +// CreateUser mocks base method +func (m *MockUserHelper) CreateUser(arg0 users.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateUser", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateUser indicates an expected call of CreateUser +func (mr *MockUserHelperMockRecorder) CreateUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockUserHelper)(nil).CreateUser), arg0) +} + +// GetUser mocks base method +func (m *MockUserHelper) GetUser(arg0 int) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUser", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUser indicates an expected call of GetUser +func (mr *MockUserHelperMockRecorder) GetUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUserHelper)(nil).GetUser), arg0) +} + +// GetUserByEmail mocks base method +func (m *MockUserHelper) GetUserByEmail(arg0 string) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserByEmail", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserByEmail indicates an expected call of GetUserByEmail +func (mr *MockUserHelperMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockUserHelper)(nil).GetUserByEmail), arg0) +} + +// HashPassword mocks base method +func (m *MockUserHelper) HashPassword(arg0 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HashPassword", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// HashPassword indicates an expected call of HashPassword +func (mr *MockUserHelperMockRecorder) HashPassword(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashPassword", reflect.TypeOf((*MockUserHelper)(nil).HashPassword), arg0) +} + +// IssueActivationRequest mocks base method +func (m *MockUserHelper) IssueActivationRequest(arg0 string, arg1 int, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IssueActivationRequest", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// IssueActivationRequest indicates an expected call of IssueActivationRequest +func (mr *MockUserHelperMockRecorder) IssueActivationRequest(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueActivationRequest", reflect.TypeOf((*MockUserHelper)(nil).IssueActivationRequest), arg0, arg1, arg2) +} + +// IssueOneTimePasswordRequest mocks base method +func (m *MockUserHelper) IssueOneTimePasswordRequest(arg0 string, arg1 int, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IssueOneTimePasswordRequest", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// IssueOneTimePasswordRequest indicates an expected call of IssueOneTimePasswordRequest +func (mr *MockUserHelperMockRecorder) IssueOneTimePasswordRequest(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueOneTimePasswordRequest", reflect.TypeOf((*MockUserHelper)(nil).IssueOneTimePasswordRequest), arg0, arg1, arg2) +} + +// IssueResetRequest mocks base method +func (m *MockUserHelper) IssueResetRequest(arg0 string, arg1 int, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IssueResetRequest", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// IssueResetRequest indicates an expected call of IssueResetRequest +func (mr *MockUserHelperMockRecorder) IssueResetRequest(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueResetRequest", reflect.TypeOf((*MockUserHelper)(nil).IssueResetRequest), arg0, arg1, arg2) +} + +// ListUsers mocks base method +func (m *MockUserHelper) ListUsers(arg0 bool, arg1 []int) ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUsers", arg0, arg1) + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListUsers indicates an expected call of ListUsers +func (mr *MockUserHelperMockRecorder) ListUsers(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsers", reflect.TypeOf((*MockUserHelper)(nil).ListUsers), arg0, arg1) +} + +// Login mocks base method +func (m *MockUserHelper) Login(arg0 string, arg1 int, arg2 users.User) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Login", arg0, arg1, arg2) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Login indicates an expected call of Login +func (mr *MockUserHelperMockRecorder) Login(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockUserHelper)(nil).Login), arg0, arg1, arg2) +} + +// NewEmptyUser mocks base method +func (m *MockUserHelper) NewEmptyUser() users.User { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewEmptyUser") + ret0, _ := ret[0].(users.User) + return ret0 +} + +// NewEmptyUser indicates an expected call of NewEmptyUser +func (mr *MockUserHelperMockRecorder) NewEmptyUser() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewEmptyUser", reflect.TypeOf((*MockUserHelper)(nil).NewEmptyUser)) +} + +// PasswordChange mocks base method +func (m *MockUserHelper) PasswordChange(arg0 int, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PasswordChange", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// PasswordChange indicates an expected call of PasswordChange +func (mr *MockUserHelperMockRecorder) PasswordChange(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordChange", reflect.TypeOf((*MockUserHelper)(nil).PasswordChange), arg0, arg1, arg2, arg3) +} + +// PasswordChangeForced mocks base method +func (m *MockUserHelper) PasswordChangeForced(arg0 int, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PasswordChangeForced", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// PasswordChangeForced indicates an expected call of PasswordChangeForced +func (mr *MockUserHelperMockRecorder) PasswordChangeForced(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordChangeForced", reflect.TypeOf((*MockUserHelper)(nil).PasswordChangeForced), arg0, arg1, arg2) +} + +// RemoveUserFromOrganization mocks base method +func (m *MockUserHelper) RemoveUserFromOrganization(arg0 users.User, arg1 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveUserFromOrganization", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveUserFromOrganization indicates an expected call of RemoveUserFromOrganization +func (mr *MockUserHelperMockRecorder) RemoveUserFromOrganization(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveUserFromOrganization", reflect.TypeOf((*MockUserHelper)(nil).RemoveUserFromOrganization), arg0, arg1) +} + +// TokenGenerator mocks base method +func (m *MockUserHelper) TokenGenerator() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TokenGenerator") + ret0, _ := ret[0].(string) + return ret0 +} + +// TokenGenerator indicates an expected call of TokenGenerator +func (mr *MockUserHelperMockRecorder) TokenGenerator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TokenGenerator", reflect.TypeOf((*MockUserHelper)(nil).TokenGenerator)) +} + +// Update mocks base method +func (m *MockUserHelper) Update(arg0 int, arg1 users.User) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", arg0, arg1) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update +func (mr *MockUserHelperMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUserHelper)(nil).Update), arg0, arg1) +} + +// UpdateUser mocks base method +func (m *MockUserHelper) UpdateUser(arg0 users.User) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUser", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateUser indicates an expected call of UpdateUser +func (mr *MockUserHelperMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockUserHelper)(nil).UpdateUser), arg0) +} + +// UseToken mocks base method +func (m *MockUserHelper) UseToken(arg0 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseToken", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// UseToken indicates an expected call of UseToken +func (mr *MockUserHelperMockRecorder) UseToken(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseToken", reflect.TypeOf((*MockUserHelper)(nil).UseToken), arg0) +} + +// ValidatePassword mocks base method +func (m *MockUserHelper) ValidatePassword(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidatePassword", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidatePassword indicates an expected call of ValidatePassword +func (mr *MockUserHelperMockRecorder) ValidatePassword(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatePassword", reflect.TypeOf((*MockUserHelper)(nil).ValidatePassword), arg0) +} + +// ValidateToken mocks base method +func (m *MockUserHelper) ValidateToken(arg0 string) (int, int, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateToken", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(string) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// ValidateToken indicates an expected call of ValidateToken +func (mr *MockUserHelperMockRecorder) ValidateToken(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateToken", reflect.TypeOf((*MockUserHelper)(nil).ValidateToken), arg0) +} diff --git a/mocks/mock_users_repo.go b/mocks/mock_users_repo.go new file mode 100644 index 0000000..00cd6dd --- /dev/null +++ b/mocks/mock_users_repo.go @@ -0,0 +1,165 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/users (interfaces: Repo) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + users "github.com/reaction-eng/restlib/users" + reflect "reflect" +) + +// MockUserRepo is a mock of Repo interface +type MockUserRepo struct { + ctrl *gomock.Controller + recorder *MockUserRepoMockRecorder +} + +// MockUserRepoMockRecorder is the mock recorder for MockUserRepo +type MockUserRepoMockRecorder struct { + mock *MockUserRepo +} + +// NewMockUserRepo creates a new mock instance +func NewMockUserRepo(ctrl *gomock.Controller) *MockUserRepo { + mock := &MockUserRepo{ctrl: ctrl} + mock.recorder = &MockUserRepoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockUserRepo) EXPECT() *MockUserRepoMockRecorder { + return m.recorder +} + +// ActivateUser mocks base method +func (m *MockUserRepo) ActivateUser(arg0 users.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ActivateUser", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ActivateUser indicates an expected call of ActivateUser +func (mr *MockUserRepoMockRecorder) ActivateUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActivateUser", reflect.TypeOf((*MockUserRepo)(nil).ActivateUser), arg0) +} + +// AddUser mocks base method +func (m *MockUserRepo) AddUser(arg0 users.User) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddUser", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddUser indicates an expected call of AddUser +func (mr *MockUserRepoMockRecorder) AddUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUser", reflect.TypeOf((*MockUserRepo)(nil).AddUser), arg0) +} + +// AddUserToOrganization mocks base method +func (m *MockUserRepo) AddUserToOrganization(arg0 users.User, arg1 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddUserToOrganization", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddUserToOrganization indicates an expected call of AddUserToOrganization +func (mr *MockUserRepoMockRecorder) AddUserToOrganization(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUserToOrganization", reflect.TypeOf((*MockUserRepo)(nil).AddUserToOrganization), arg0, arg1) +} + +// GetUser mocks base method +func (m *MockUserRepo) GetUser(arg0 int) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUser", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUser indicates an expected call of GetUser +func (mr *MockUserRepoMockRecorder) GetUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUserRepo)(nil).GetUser), arg0) +} + +// GetUserByEmail mocks base method +func (m *MockUserRepo) GetUserByEmail(arg0 string) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserByEmail", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserByEmail indicates an expected call of GetUserByEmail +func (mr *MockUserRepoMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockUserRepo)(nil).GetUserByEmail), arg0) +} + +// ListUsers mocks base method +func (m *MockUserRepo) ListUsers(arg0 bool, arg1 []int) ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUsers", arg0, arg1) + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListUsers indicates an expected call of ListUsers +func (mr *MockUserRepoMockRecorder) ListUsers(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsers", reflect.TypeOf((*MockUserRepo)(nil).ListUsers), arg0, arg1) +} + +// NewEmptyUser mocks base method +func (m *MockUserRepo) NewEmptyUser() users.User { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewEmptyUser") + ret0, _ := ret[0].(users.User) + return ret0 +} + +// NewEmptyUser indicates an expected call of NewEmptyUser +func (mr *MockUserRepoMockRecorder) NewEmptyUser() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewEmptyUser", reflect.TypeOf((*MockUserRepo)(nil).NewEmptyUser)) +} + +// RemoveUserFromOrganization mocks base method +func (m *MockUserRepo) RemoveUserFromOrganization(arg0 users.User, arg1 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveUserFromOrganization", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveUserFromOrganization indicates an expected call of RemoveUserFromOrganization +func (mr *MockUserRepoMockRecorder) RemoveUserFromOrganization(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveUserFromOrganization", reflect.TypeOf((*MockUserRepo)(nil).RemoveUserFromOrganization), arg0, arg1) +} + +// UpdateUser mocks base method +func (m *MockUserRepo) UpdateUser(arg0 users.User) (users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUser", arg0) + ret0, _ := ret[0].(users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateUser indicates an expected call of UpdateUser +func (mr *MockUserRepoMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockUserRepo)(nil).UpdateUser), arg0) +} diff --git a/mocks/testRouter.go b/mocks/testRouter.go new file mode 100644 index 0000000..b055733 --- /dev/null +++ b/mocks/testRouter.go @@ -0,0 +1,85 @@ +package mocks + +import ( + "context" + "net/http" + + "github.com/gorilla/mux" + + "github.com/reaction-eng/restlib/users" + + "github.com/reaction-eng/restlib/utils" + + "github.com/reaction-eng/restlib/routing" +) + +type TestRouter struct { + router *mux.Router + routeMap map[string]routing.Route + userId *int + orgId *int +} + +func NewTestRouter(routerProducer routing.RouteProducer) *TestRouter { + return NewTestRouterWithUserId(routerProducer, -1, -1) +} +func NewTestRouterWithUserId(routerProducer routing.RouteProducer, userId int, orgId int) *TestRouter { + + var testRouter *TestRouter + if userId >= 0 { + testRouter = &TestRouter{ + mux.NewRouter().StrictSlash(true), + make(map[string]routing.Route, 0), + &userId, + &orgId, + } + } else { + // not logged in + testRouter = &TestRouter{ + mux.NewRouter().StrictSlash(true), + make(map[string]routing.Route, 0), + nil, + nil, + } + } + + for _, route := range routerProducer.GetRoutes() { + muxRoute := testRouter.router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(route.HandlerFunc) + + testRouter.routeMap[muxRoute.GetName()] = route + } + + return testRouter + +} + +func NewTestRouterWithUser(routerProducer routing.RouteProducer, user users.User, orgId int) *TestRouter { + if user == nil { + return NewTestRouter(routerProducer) + } else { + return NewTestRouterWithUserId(routerProducer, user.Id(), orgId) + } +} + +func (router *TestRouter) Handle(w http.ResponseWriter, r *http.Request) *routing.Route { + // Update the context + if router.userId != nil { + ctx := context.WithValue(r.Context(), utils.UserKey, *router.userId) + ctx = context.WithValue(ctx, utils.OrganizationKey, *router.orgId) + r = r.WithContext(ctx) + } + + router.router.ServeHTTP(w, r) + + var match mux.RouteMatch + if router.router.Match(r, &match) { + route := router.routeMap[match.Route.GetName()] + return &route + } + + return nil +} diff --git a/notification/NotifierCalendar.go b/notification/NotifierCalendar.go index 11c7de1..786492b 100644 --- a/notification/NotifierCalendar.go +++ b/notification/NotifierCalendar.go @@ -5,13 +5,14 @@ package notification import ( "context" + "io/ioutil" + "log" + "net/http" + "github.com/reaction-eng/restlib/users" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/calendar/v3" - "io/ioutil" - "log" - "net/http" ) type CalendarNotifier struct { @@ -26,7 +27,7 @@ func (notif *CalendarNotifier) Notify(notification Notification, user users.User //create new Calendar event. newEvent := &calendar.Event{ - Summary: "REI Event", + Summary: "REI gEvent", Location: "University of Utah", Description: notification.Message, Start: &calendar.EventDateTime{ diff --git a/notification/NotifierWebPush.go b/notification/NotifierWebPush.go index 4919a7f..aec5290 100644 --- a/notification/NotifierWebPush.go +++ b/notification/NotifierWebPush.go @@ -6,6 +6,7 @@ package notification import ( "database/sql" "encoding/json" + "github.com/SherClockHolmes/webpush-go" "github.com/reaction-eng/restlib/preferences" "github.com/reaction-eng/restlib/users" @@ -34,8 +35,11 @@ func NewWebPushNotifier(preferencesJsonFile string, db *sql.DB) *WebPushNotifier func (notif *WebPushNotifier) Notify(notification Notification, user users.User) error { //Get user preferences. - options := preferences.LoadOptionsGroup(notif.PreferencesJson) - sqlConnectiont := preferences.NewRepoMySql(notif.db, "userpref", options) + options, err := preferences.LoadOptionsGroup(notif.PreferencesJson) + if err != nil { + return err + } + sqlConnectiont, err := preferences.NewRepoMySql(notif.db, options) usrsPref, err := sqlConnectiont.GetPreferences(user) //Get user's subscription to send notification to. diff --git a/passwords/BasicHelper.go b/passwords/basicHelper.go similarity index 73% rename from passwords/BasicHelper.go rename to passwords/basicHelper.go index fe1ae4d..59ce099 100644 --- a/passwords/BasicHelper.go +++ b/passwords/basicHelper.go @@ -7,18 +7,13 @@ import ( "crypto/rand" "errors" "fmt" - "log" "strings" - "github.com/reaction-eng/restlib/configuration" "github.com/dgrijalva/jwt-go" + "github.com/reaction-eng/restlib/configuration" "golang.org/x/crypto/bcrypt" ) -/** -File of static support functions for passwords creating, editing, hashing, etc. -*/ - type BasicHelper struct { //Keep a global password config jwtTokenPassword []byte @@ -28,34 +23,26 @@ type BasicHelper struct { JWT claims struct */ type Token struct { - UserId int - Email string + UserId int + OrganizationId int + Email string jwt.StandardClaims } //Load it during init -func NewBasicHelper(configFiles ...string) *BasicHelper { - //Load in a config file - config, err := configuration.NewConfiguration(configFiles...) - - //If there is an error - if err != nil { - log.Fatal("Cannot load config auth file: config.auth.json", err) - } - +func NewBasicHelper(configuration configuration.Configuration) (*BasicHelper, error) { //Now get the token - jwtTokenPasswordString := config.GetString("token_password") + jwtTokenPasswordString := configuration.GetString("token_password") //If it is null error if len(jwtTokenPasswordString) < 60 { - log.Fatal("The jwt token is not specified or not long enough.") - + return nil, errors.New("the jwt token is not specified or not long enough") } + //Store the byte array return &BasicHelper{ jwtTokenPassword: []byte(jwtTokenPasswordString), - } - + }, nil } /** @@ -66,16 +53,15 @@ func (helper *BasicHelper) HashPassword(password string) string { //Hash the password, there should be a salt hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(hashedPassword) - } /** Support function to generate a JWT token */ -func (helper *BasicHelper) CreateJWTToken(userId int, email string) string { +func (helper *BasicHelper) CreateJWTToken(userId int, organizationId int, email string) string { //Create new JWT token for the newly registered account - tk := &Token{UserId: userId, Email: email} + tk := &Token{UserId: userId, OrganizationId: organizationId, Email: email} token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) tokenString, _ := token.SignedString(helper.jwtTokenPassword) @@ -84,7 +70,7 @@ func (helper *BasicHelper) CreateJWTToken(userId int, email string) string { } /** - Compare passwords. Determine if they match + Compare mysql. Determine if they match */ func (helper *BasicHelper) ComparePasswords(currentPwHash string, testingPassword string) bool { @@ -95,32 +81,28 @@ func (helper *BasicHelper) ComparePasswords(currentPwHash string, testingPasswor } else { return true } - } /** * Get a random token */ func (helper *BasicHelper) TokenGenerator() string { - b := make([]byte, 4) + b := make([]byte, 32) rand.Read(b) return fmt.Sprintf("%x", b) } -/** - Compare passwords. Determine if they match -*/ -func (helper *BasicHelper) ValidateToken(tokenHeader string) (int, string, error) { +func (helper *BasicHelper) ValidateToken(tokenHeader string) (int, int, string, error) { //Token is missing, returns with error code 403 Unauthorized if tokenHeader == "" { - return -1, "", errors.New("auth_missing_token") + return -1, -1, "", errors.New("auth_missing_token") } //Now split the token to get the useful part splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement if len(splitted) != 2 { - return -1, "", errors.New("auth_malformed_token") + return -1, -1, "", errors.New("auth_malformed_token") } @@ -138,19 +120,17 @@ func (helper *BasicHelper) ValidateToken(tokenHeader string) (int, string, error //check for mailformed data if err != nil { //Malformed token, returns with http code 403 as usual - return -1, "", errors.New("auth_malformed_token") + return -1, -1, "", errors.New("auth_malformed_token") } //Token is invalid, maybe not signed on this server if !token.Valid { //Return the error - return -1, "", errors.New("auth_forbidden") - + return -1, -1, "", errors.New("auth_forbidden") } - return tk.UserId, tk.Email, nil - + return tk.UserId, tk.OrganizationId, tk.Email, nil } /** diff --git a/passwords/basicHelper_test.go b/passwords/basicHelper_test.go new file mode 100644 index 0000000..8c6d201 --- /dev/null +++ b/passwords/basicHelper_test.go @@ -0,0 +1,313 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package passwords_test + +import ( + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/reaction-eng/restlib/passwords" + "github.com/stretchr/testify/assert" +) + +func TestNewBasicHelper(t *testing.T) { + testCases := []struct { + configuration map[string]string + expectedError error + expectResult bool + }{ + { + configuration: map[string]string{"token_password": "UOGWZSRAODMTCMYZOUFXUOGWZSRAODMTCMYZOUFXUOGWZSRAODMTCMYZOUFX"}, + expectedError: nil, + expectResult: true, + }, + { + configuration: map[string]string{"token_password": "UOGWZSRAODMTCZSRAODMTCMYZOUFX"}, + expectedError: errors.New("the jwt token is not specified or not long enough"), + expectResult: false, + }, + { + configuration: map[string]string{"token_password": ""}, + expectedError: errors.New("the jwt token is not specified or not long enough"), + expectResult: false, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + for k, v := range testCase.configuration { + mockConfiguration.EXPECT().GetString(k).Return(v).Times(1) + } + + // act + helper, err := passwords.NewBasicHelper(mockConfiguration) + + // assert + assert.Equal(t, testCase.expectedError, err) + if testCase.expectResult { + assert.NotNil(t, helper) + } else { + assert.Nil(t, helper) + } + } +} + +func setupBasicHelper(t *testing.T, mockCtrl *gomock.Controller) *passwords.BasicHelper { + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetString("token_password").Return("UOGWZSRAODMTCMYZOUFXUOGWZSRAODMTCMYZOUFXUOGWZSRAODMTCMYZOUFX").Times(1) + + helper, err := passwords.NewBasicHelper(mockConfiguration) + if err != nil { + t.Fatal(err) + } + + return helper +} + +func TestBasicHelper_HashPassword(t *testing.T) { + testCases := []struct { + input string + expectedLength int + }{ + { + "123456", + 60, + }, + { + "alphaBetaGama", + 60, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + helper := setupBasicHelper(t, mockCtrl) + + // act + output := helper.HashPassword(testCase.input) + + // assert + assert.Equal(t, testCase.expectedLength, len(output)) + } +} + +func TestBasicHelper_CreateJWTToken(t *testing.T) { + testCases := []struct { + userId int + orgId int + email string + expectedResult string + }{ + { + 42, + 65, + "example@example.com", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjQyLCJPcmdhbml6YXRpb25JZCI6NjUsIkVtYWlsIjoiZXhhbXBsZUBleGFtcGxlLmNvbSJ9.8xvP_tiVqrowq85_t4eoJqh0PXJGqOY1mG6ixKcFpqw", + }, + { + 102, + 23, + "example2@example.com", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEwMiwiT3JnYW5pemF0aW9uSWQiOjIzLCJFbWFpbCI6ImV4YW1wbGUyQGV4YW1wbGUuY29tIn0.s0IMktQm5I3DJ9Bixdyd42-q3dEbj6xAFz_v7AOaLRY", + }, + { + 98, + 346, + "matt@example.com", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJPcmdhbml6YXRpb25JZCI6MzQ2LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.pzf0sT2F77gH-1Ghqw_bwWrxSDbpc85FD1nXxBByHQc", + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + helper := setupBasicHelper(t, mockCtrl) + + // act + output := helper.CreateJWTToken(testCase.userId, testCase.orgId, testCase.email) + + // assert + assert.Equal(t, testCase.expectedResult, output) + } +} + +func TestBasicHelper_ComparePasswords(t *testing.T) { + testCases := []struct { + currentPwHash string + testPassword string + equals bool + }{ + { + "$2y$10$Dh6eCPAO43w2Ta72Mqu./.YDjMjXFyNd3Jl4AAyqukC/iDblorUNu", + "123456", + true, + }, + { + "$2y$10$Dh6eCPAO43w2Ta72Mqu./.YDjMjXFyNd3Jl4AAyqukC/iDblorUNu", + "12345", + false, + }, + { + "$2y$10$mgBz16gBtT1UodLYcapX7ORIZR7Il3bwulDqz5fGQHqCIh2WQ5No2", + "alphaBetaGama", + true, + }, + { + "$2y$10$mgBz16gBtT1UodLYcapX7ORIZR7Il3bwulDqz5fGQHqCIh2WQ5No2", + "alphafBetaGama", + false, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + helper := setupBasicHelper(t, mockCtrl) + + // act + equals := helper.ComparePasswords(testCase.currentPwHash, testCase.testPassword) + + // assert + assert.Equal(t, testCase.equals, equals) + } +} + +func TestBasicHelper_TokenGenerator(t *testing.T) { + // arrange + testRounds := 1000 + + previous := make([]string, 0) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + helper := setupBasicHelper(t, mockCtrl) + + // act + // assert + for i := 0; i < testRounds; i++ { + newToken := helper.TokenGenerator() + + for _, value := range previous { + assert.NotEqual(t, newToken, value) + } + previous = append(previous, newToken) + } +} + +func TestBasicHelper_ValidateToken(t *testing.T) { + testCases := []struct { + token string + userId int + orgId int + email string + error error + }{ + { + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20iLCJPcmdhbml6YXRpb25JZCI6MjI0M30.Xb_scxUShDoaBRmKqxSzwrdPlZq8fzHNBy39fe6rrGI", + 98, + 2243, + "matt@example.com", + nil, + }, + { + "", + -1, + -1, + "", + errors.New("auth_missing_token"), + }, + { + "Be", + -1, + -1, + "", + errors.New("auth_malformed_token"), + }, + { + "BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + -1, + -1, + "", + errors.New("auth_malformed_token"), + }, + { + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ2.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + -1, + -1, + "", + errors.New("auth_malformed_token"), + }, + { + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscEdKJqdhHfMgiOSHiu_2jOBsOLyA", + -1, + -1, + "", + errors.New("auth_malformed_token"), + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + helper := setupBasicHelper(t, mockCtrl) + + // act + userId, orgId, email, err := helper.ValidateToken(testCase.token) + + // assert + assert.Equal(t, testCase.userId, userId) + assert.Equal(t, testCase.orgId, orgId) + assert.Equal(t, testCase.email, email) + assert.Equal(t, testCase.error, err) + } +} + +func TestBasicHelper_ValidatePassword(t *testing.T) { + testCases := []struct { + password string + error error + }{ + { + "example1252352", + nil, + }, + { + "abcdef", + nil, + }, + { + "abcde", + errors.New("validate_password_insufficient"), + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + helper := setupBasicHelper(t, mockCtrl) + + // act + error := helper.ValidatePassword(testCase.password) + + // assert + assert.Equal(t, testCase.error, error) + } +} diff --git a/passwords/Helper.go b/passwords/helper.go similarity index 60% rename from passwords/Helper.go rename to passwords/helper.go index 6cde913..e1f7036 100644 --- a/passwords/Helper.go +++ b/passwords/helper.go @@ -3,11 +3,13 @@ package passwords +//go:generate mockgen -destination=../mocks/mock_helper.go -package=mocks github.com/reaction-eng/restlib/passwords Helper + type Helper interface { HashPassword(password string) string - CreateJWTToken(userId int, email string) string + CreateJWTToken(userId int, orgId int, email string) string ComparePasswords(currentPwHash string, testingPassword string) bool TokenGenerator() string - ValidateToken(tokenHeader string) (int, string, error) + ValidateToken(tokenHeader string) (int, int, string, error) ValidatePassword(password string) error } diff --git a/passwords/ResetRepo.go b/passwords/resetRepo.go similarity index 67% rename from passwords/ResetRepo.go rename to passwords/resetRepo.go index 85df60d..674e3df 100644 --- a/passwords/ResetRepo.go +++ b/passwords/resetRepo.go @@ -3,39 +3,23 @@ package passwords -/** -Define an interface that all Calc Repos must follow -*/ -type ResetRepo interface { +//go:generate mockgen -destination=../mocks/mock_resetRepo.go -package=mocks github.com/reaction-eng/restlib/passwords ResetRepo - /** - Issues a request for the user - */ +type ResetRepo interface { IssueResetRequest(token string, userId int, email string) error - /** - Issues a request for the user - */ - CheckForResetToken(userId int, reset_token string) (int, error) + CheckForResetToken(userId int, resetToken string) (int, error) - /** - Issues a request for the user - */ IssueActivationRequest(token string, userId int, email string) error - /** - Issues a request for the user - */ CheckForActivationToken(userId int, activationToken string) (int, error) - /** - Issues a request for the user - */ + IssueOneTimePasswordRequest(token string, userId int, email string) error + + CheckForOneTimePasswordToken(userId int, activationToken string) (int, error) + UseToken(id int) error - /** - Allow databases to be closed - */ CleanUp() } diff --git a/passwords/ResetRepoSql.go b/passwords/resetRepoSql.go similarity index 51% rename from passwords/ResetRepoSql.go rename to passwords/resetRepoSql.go index 9eac84c..ab72fe7 100644 --- a/passwords/ResetRepoSql.go +++ b/passwords/resetRepoSql.go @@ -4,33 +4,34 @@ package passwords import ( - "github.com/reaction-eng/restlib/configuration" - "github.com/reaction-eng/restlib/email" "database/sql" "errors" - "log" "time" + + "github.com/reaction-eng/restlib/configuration" + "github.com/reaction-eng/restlib/email" ) -/** -Define a struct for Repo for use with users -*/ +const TableName = "resetrequests" + +var TokenExpired = errors.New("token_expired") + type ResetRepoSql struct { //Hold on to the sql databased db *sql.DB - //Also store the table name - tableName string - //We need the emailer - emailer email.Interface - resetEmailConfig PasswordResetConfig - activationEmailConfig PasswordResetConfig + emailer email.Emailer + resetEmailConfig PasswordResetConfig + activationEmailConfig PasswordResetConfig + oneTimePasswordEmailConfig PasswordResetConfig - //Store the required statements to reduce comput time + //Store the required statements to reduce compute time addRequestStatement *sql.Stmt getRequestStatement *sql.Stmt rmRequestStatement *sql.Stmt + + tokenLifeSpan float64 } /** @@ -39,154 +40,152 @@ Store the type of token type tokenType int const ( - activation tokenType = 1 - reset tokenType = 2 + activation tokenType = 1 + reset tokenType = 2 + oneTimePassword tokenType = 3 ) -//Provide a method to make a new UserRepoSql -func NewRepoMySql(db *sql.DB, tableName string, emailer email.Interface, configFile string) *ResetRepoSql { - - //Create a config - config, err := configuration.NewConfiguration(configFile) - - //If there is no error - if err != nil { - log.Fatal(err) - } +func NewRepoMySql(db *sql.DB, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} + oneTimePasswordEmailConfig := PasswordResetConfig{} //Pull from the config - config.GetStruct("password_reset", &resetEmailConfig) - config.GetStruct("user_activation", &activationEmailConfig) + err := configuration.GetStruct("password_reset", &resetEmailConfig) + if err != nil { + return nil, err + } + configuration.GetStruct("user_activation", &activationEmailConfig) + if err != nil { + return nil, err + } + configuration.GetStruct("one_time_password", &oneTimePasswordEmailConfig) + if err != nil { + return nil, err + } + tokenLifeSpan, err := configuration.GetFloat("tokenLifeSpan") + if err != nil { + return nil, err + } //Define a new repo newRepo := ResetRepoSql{ - db: db, - tableName: tableName, - emailer: emailer, - resetEmailConfig: resetEmailConfig, - activationEmailConfig: activationEmailConfig, - } - - //Create the table if it is not already there - //Create a table - _, err = db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(id int NOT NULL AUTO_INCREMENT, userId int, email TEXT, token TEXT, issued DATE, type INT, PRIMARY KEY (id) )") - if err != nil { - log.Fatal(err) + db: db, + emailer: emailer, + resetEmailConfig: resetEmailConfig, + activationEmailConfig: activationEmailConfig, + oneTimePasswordEmailConfig: oneTimePasswordEmailConfig, + tokenLifeSpan: tokenLifeSpan, } //Add request data to table - addRequest, err := db.Prepare("INSERT INTO " + tableName + "(userId,email, token, issued, type) VALUES (?, ?, ?, ?, ?)") - //Check for error + addRequest, err := db.Prepare("INSERT INTO " + TableName + " (userId,email, token, issued, type) VALUES (?, ?, ?, ?, ?)") if err != nil { - log.Fatal(err) + return nil, err } + //Store it newRepo.addRequestStatement = addRequest //pull the request from the table - getRequest, err := db.Prepare("SELECT * FROM " + tableName + " where userId = ? AND token = ? AND type = ?") - //Check for error + getRequest, err := db.Prepare("SELECT * FROM " + TableName + " where userId = ? AND token = ? AND type = ?") if err != nil { - log.Fatal(err) + return nil, err } + //Store it newRepo.getRequestStatement = getRequest //pull the request from the table - rmRequest, err := db.Prepare("delete FROM " + tableName + " where id = ? limit 1") - //Check for error + rmRequest, err := db.Prepare("delete FROM " + TableName + " where id = ? limit 1") if err != nil { - log.Fatal(err) + return nil, err } + //Store it newRepo.rmRequestStatement = rmRequest //Return a point - return &newRepo + return &newRepo, nil } -//Provide a method to make a new UserRepoSql -func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Interface, configFile ...string) *ResetRepoSql { - - //Create a config - config, err := configuration.NewConfiguration(configFile...) - - //If there is no error - if err != nil { - log.Fatal(err) - } - +func NewRepoPostgresSql(db *sql.DB, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} + oneTimePasswordEmailConfig := PasswordResetConfig{} //Pull from the config - config.GetStruct("password_reset", &resetEmailConfig) - config.GetStruct("user_activation", &activationEmailConfig) + err := configuration.GetStruct("password_reset", &resetEmailConfig) + if err != nil { + return nil, err + } + configuration.GetStruct("user_activation", &activationEmailConfig) + if err != nil { + return nil, err + } + configuration.GetStruct("one_time_password", &oneTimePasswordEmailConfig) + if err != nil { + return nil, err + } + tokenLifeSpan, err := configuration.GetFloat("tokenLifeSpan") + if err != nil { + return nil, err + } //Define a new repo newRepo := ResetRepoSql{ - db: db, - tableName: tableName, - emailer: emailer, - resetEmailConfig: resetEmailConfig, - activationEmailConfig: activationEmailConfig, - } - - //Create the table if it is not already there - //Create a table - _, err = db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(id SERIAL PRIMARY KEY, userId int NOT NULL, email TEXT NOT NULL, token TEXT NOT NULL,issued DATE NOT NULL, type int NOT NULL)") - - if err != nil { - log.Fatal(err) + db: db, + emailer: emailer, + resetEmailConfig: resetEmailConfig, + activationEmailConfig: activationEmailConfig, + oneTimePasswordEmailConfig: oneTimePasswordEmailConfig, + tokenLifeSpan: tokenLifeSpan, } //Add request data to table - addRequest, err := db.Prepare("INSERT INTO " + tableName + "(userId,email, token, issued, type) VALUES ($1, $2, $3, $4, $5)") - //Check for error + addRequest, err := db.Prepare("INSERT INTO " + TableName + "(userId,email, token, issued, type) VALUES ($1, $2, $3, $4, $5)") if err != nil { - log.Fatal(err) + return nil, err } + //Store it newRepo.addRequestStatement = addRequest //pull the request from the table - getRequest, err := db.Prepare("SELECT * FROM " + tableName + " where userId = $1 AND token = $2 AND type = $3") - //Check for error + getRequest, err := db.Prepare("SELECT * FROM " + TableName + " where userId = $1 AND token = $2 AND type = $3") if err != nil { - log.Fatal(err) + return nil, err } + //Store it newRepo.getRequestStatement = getRequest //pull the request from the table - rmRequest, err := db.Prepare("delete FROM " + tableName + " where id = $1") - //Check for error + rmRequest, err := db.Prepare("delete FROM " + TableName + " where id = $1") if err != nil { - log.Fatal(err) + return nil, err } + //Store it newRepo.rmRequestStatement = rmRequest //Return a point - return &newRepo + return &newRepo, nil } -/** -Look up the user and return if they were found -*/ func (repo *ResetRepoSql) IssueResetRequest(token string, userId int, emailAddress string) error { //Now add it to the database - //Add the info //execute the statement//(userId,name,input,flow)- "(userId,email, token, issued) _, err := repo.addRequestStatement.Exec(userId, emailAddress, token, time.Now(), reset) + if err != nil { + return err + } //Make the email header header := email.HeaderInfo{ @@ -201,21 +200,19 @@ func (repo *ResetRepoSql) IssueResetRequest(token string, userId int, emailAddre } //Now email - err = repo.emailer.SendEmailTemplateFile(&header, repo.resetEmailConfig.Template, resetInfo, nil) + err = repo.emailer.SendTemplateFile(&header, repo.resetEmailConfig.Template, resetInfo, nil) - //Return the user calcs return err } -/** -Look up the user and return if they were found -*/ func (repo *ResetRepoSql) IssueActivationRequest(token string, userId int, emailAddress string) error { //Now add it to the database - //Add the info //execute the statement//(userId,name,input,flow)- "(userId,email, token, issued) _, err := repo.addRequestStatement.Exec(userId, emailAddress, token, time.Now(), activation) + if err != nil { + return err + } //Make the email header header := email.HeaderInfo{ @@ -230,22 +227,44 @@ func (repo *ResetRepoSql) IssueActivationRequest(token string, userId int, email } //Now email - err = repo.emailer.SendEmailTemplateFile(&header, repo.activationEmailConfig.Template, resetInfo, nil) + err = repo.emailer.SendTemplateFile(&header, repo.activationEmailConfig.Template, resetInfo, nil) + + return err +} + +func (repo *ResetRepoSql) IssueOneTimePasswordRequest(token string, userId int, emailAddress string) error { + + //Now add it to the database + _, err := repo.addRequestStatement.Exec(userId, emailAddress, token, time.Now(), oneTimePassword) + if err != nil { + return err + } + + //Make the email header + header := email.HeaderInfo{ + Subject: repo.oneTimePasswordEmailConfig.Subject, + To: []string{emailAddress}, + } + + //Build a reset token + resetInfo := PasswordResetInfo{ + Token: token, + Email: emailAddress, + } + + //Now email + err = repo.emailer.SendTemplateFile(&header, repo.oneTimePasswordEmailConfig.Template, resetInfo, nil) - //Return the user calcs return err } -/** -Use the taken to validate -*/ func (repo *ResetRepoSql) CheckForResetToken(userId int, token string) (int, error) { //Get the id and errors id, err := repo.checkForToken(userId, token, reset) //If there is an error customize it - if err != nil { + if err != nil && err != TokenExpired { err = errors.New("password_change_forbidden") } @@ -253,16 +272,13 @@ func (repo *ResetRepoSql) CheckForResetToken(userId int, token string) (int, err } -/** -Use the taken to validate -*/ func (repo *ResetRepoSql) CheckForActivationToken(userId int, token string) (int, error) { //Get the id and errors id, err := repo.checkForToken(userId, token, activation) //If there is an error customize it - if err != nil { + if err != nil && err != TokenExpired { err = errors.New("activation_forbidden") } @@ -270,37 +286,49 @@ func (repo *ResetRepoSql) CheckForActivationToken(userId int, token string) (int } -/** -Use the taken to validate -*/ +func (repo *ResetRepoSql) CheckForOneTimePasswordToken(userId int, token string) (int, error) { + + //Get the id and errors + id, err := repo.checkForToken(userId, token, oneTimePassword) + + //If there is an error customize it + if err != nil && err != TokenExpired { + err = errors.New("oneTimePassword_login_forbidden") + } + + return id, err + +} + func (repo *ResetRepoSql) checkForToken(userId int, token string, tkType tokenType) (int, error) { //Prepare to get values //id, userId int, email TEXT, token TEXT, issued DATE, var id int - var userIdDB int - var emailDB string - var tokenDB string + var userIdDb int + var emailDb string + var tokenDb string var issued time.Time - var tokenDb tokenType + var tokenType tokenType //Get the value - err := repo.getRequestStatement.QueryRow(userId, token, tkType).Scan(&id, &userIdDB, &emailDB, &tokenDB, &issued, &tokenDb) - - //So it was correct, check the date - //TODO: check the date + err := repo.getRequestStatement.QueryRow(userId, token, tkType).Scan(&id, &userIdDb, &emailDb, &tokenDb, &issued, &tokenType) //If there is an error, assume it can't be done if err != nil { return -1, errors.New("invalid_token") } + //So it was correct, check the date + if time.Now().Sub(issued).Hours() > repo.tokenLifeSpan { + return 0, TokenExpired + } + //Make sure the user id and token match - if userId != userIdDB || tokenDB != token { + if userId != userIdDb || tokenDb != token { return -1, errors.New("invalid_token") } - //Return the user calcs return id, nil } @@ -308,30 +336,11 @@ func (repo *ResetRepoSql) UseToken(id int) error { //Remove the token _, err := repo.rmRequestStatement.Exec(id) - - if err != nil { - return err - } - - return nil + return err } -/** -Clean up the database, nothing much to do -*/ func (repo *ResetRepoSql) CleanUp() { repo.getRequestStatement.Close() repo.addRequestStatement.Close() repo.rmRequestStatement.Close() - } - -//func RepoDestroyCalc(id int) error { -// for i, t := range usersList { -// if t.Id == id { -// usersList = append(usersList[:i], usersList[i+1:]...) -// return nil -// } -// } -// return fmt.Errorf("Could not find Todo with id of %d to delete", id) -//} diff --git a/passwords/resetRepoSql_test.go b/passwords/resetRepoSql_test.go new file mode 100644 index 0000000..c45c2a0 --- /dev/null +++ b/passwords/resetRepoSql_test.go @@ -0,0 +1,737 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package passwords_test + +import ( + "database/sql" + "database/sql/driver" + "errors" + "testing" + "time" + + "github.com/reaction-eng/restlib/email" + + "github.com/reaction-eng/restlib/passwords" + "github.com/stretchr/testify/assert" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestNewRepoMySql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("INSERT INTO " + passwords.TableName) + mock.ExpectPrepare("SELECT (.+) FROM " + passwords.TableName) + mock.ExpectPrepare("delete FROM " + passwords.TableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockEmailer := mocks.NewMockEmailer(mockCtrl) + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStruct("password_reset", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "test email subject" + as.Template = "test email template" + }) + mockConfiguration.EXPECT().GetStruct("user_activation", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "test email subject" + as.Template = "test email template" + }) + mockConfiguration.EXPECT().GetStruct("one_time_password", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "one time email subject" + as.Template = "one time email template" + }) + mockConfiguration.EXPECT().GetFloat("tokenLifeSpan").Times(1).Return(float64(24), nil) + + // act + repoMySql, err := passwords.NewRepoMySql(db, mockEmailer, mockConfiguration) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func TestNewRepoPostgresSql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("INSERT INTO " + passwords.TableName) + mock.ExpectPrepare("SELECT (.+) FROM " + passwords.TableName) + mock.ExpectPrepare("delete FROM " + passwords.TableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockEmailer := mocks.NewMockEmailer(mockCtrl) + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStruct("password_reset", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "test email subject" + as.Template = "test email template" + }) + mockConfiguration.EXPECT().GetStruct("user_activation", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "test email subject" + as.Template = "test email template" + }) + mockConfiguration.EXPECT().GetStruct("one_time_password", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "one time email subject" + as.Template = "one time email template" + }) + mockConfiguration.EXPECT().GetFloat("tokenLifeSpan").Times(1).Return(float64(24), nil) + + // act + repoMySql, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func setupSqlMock(t *testing.T, mockCtrl *gomock.Controller, tableName string) (*sql.DB, sqlmock.Sqlmock, *mocks.MockEmailer, *mocks.MockConfiguration) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + mock.ExpectPrepare("INSERT INTO " + tableName) + mock.ExpectPrepare("SELECT (.+) FROM " + tableName) + mock.ExpectPrepare("delete FROM " + tableName) + + mockEmailer := mocks.NewMockEmailer(mockCtrl) + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStruct("password_reset", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "password_reset_subject" + as.Template = "password_reset_template" + }) + mockConfiguration.EXPECT().GetStruct("user_activation", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "user_activation_subject" + as.Template = "user_activation_template" + }) + mockConfiguration.EXPECT().GetStruct("one_time_password", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "one time email subject" + as.Template = "one time email template" + }) + mockConfiguration.EXPECT().GetFloat("tokenLifeSpan").Times(1).Return(float64(24), nil) + + return db, mock, mockEmailer, mockConfiguration +} + +type AnyTime struct{} + +// Match satisfies sqlmock.Argument interface +func (a AnyTime) Match(v driver.Value) bool { + _, ok := v.(time.Time) + return ok +} + +func TestResetRepoSql_IssueResetRequest(t *testing.T) { + testCases := []struct { + token string + userId int + emailAddress string + execError error + emailError error + }{ + { + "exampleToken123", + 100, + "test@example.com", + nil, + nil, + }, + { + "exampleToken123", + 100, + "test@example.com", + errors.New("exec error"), + nil, + }, + { + "exampleToken123", + 100, + "test@example.com", + errors.New("exec error"), + errors.New("email error"), + }, + { + "exampleToken123", + 100, + "test@example.com", + nil, + errors.New("email error"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) + + repo, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + emailHeader := email.HeaderInfo{ + Subject: "password_reset_subject", + To: []string{testCase.emailAddress}, + } + + resetInfo := passwords.PasswordResetInfo{ + Token: testCase.token, + Email: testCase.emailAddress, + } + + if testCase.execError == nil { // this should only be called if the test case is nil + mockEmailer.EXPECT().SendTemplateFile(&emailHeader, "password_reset_template", resetInfo, nil).Times(1).Return(testCase.emailError) + } + + dbMock.ExpectExec("INSERT INTO "+passwords.TableName). + WithArgs(testCase.userId, testCase.emailAddress, testCase.token, AnyTime{}, 2). + WillReturnResult(sqlmock.NewResult(0, 0)). + WillReturnError(testCase.execError) + + // act + err = repo.IssueResetRequest(testCase.token, testCase.userId, testCase.emailAddress) + + // assert + if testCase.execError != nil { + assert.Equal(t, testCase.execError, err) + } else if testCase.emailError != nil { + assert.Equal(t, testCase.emailError, err) + } else { + assert.Nil(t, err) + } + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_IssueActivationRequest(t *testing.T) { + testCases := []struct { + token string + userId int + emailAddress string + execError error + emailError error + }{ + { + "exampleToken123", + 100, + "test@example.com", + nil, + nil, + }, + { + "exampleToken123", + 100, + "test@example.com", + errors.New("exec error"), + nil, + }, + { + "exampleToken123", + 100, + "test@example.com", + errors.New("exec error"), + errors.New("email error"), + }, + { + "exampleToken123", + 100, + "test@example.com", + nil, + errors.New("email error"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) + + repo, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + emailHeader := email.HeaderInfo{ + Subject: "user_activation_subject", + To: []string{testCase.emailAddress}, + } + + resetInfo := passwords.PasswordResetInfo{ + Token: testCase.token, + Email: testCase.emailAddress, + } + + if testCase.execError == nil { // this should only be called if the test case is nil + mockEmailer.EXPECT().SendTemplateFile(&emailHeader, "user_activation_template", resetInfo, nil).Times(1).Return(testCase.emailError) + } + + dbMock.ExpectExec("INSERT INTO "+passwords.TableName). + WithArgs(testCase.userId, testCase.emailAddress, testCase.token, AnyTime{}, 1). // one for activation + WillReturnResult(sqlmock.NewResult(0, 0)). + WillReturnError(testCase.execError) + + // act + err = repo.IssueActivationRequest(testCase.token, testCase.userId, testCase.emailAddress) + + // assert + if testCase.execError != nil { + assert.Equal(t, testCase.execError, err) + } else if testCase.emailError != nil { + assert.Equal(t, testCase.emailError, err) + } else { + assert.Nil(t, err) + } + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_CheckForResetToken(t *testing.T) { + testCases := []struct { + userId int + token string + userIdDb int + tokenDb string + issueTime time.Time + queryError error + rowId int + expectedRowId int + expectedError error + }{ + { + 100, + "example token", + 100, + "example token", + time.Now().Add(-25 * time.Hour), + nil, + 1023, + 0, + errors.New("token_expired"), + }, + { + 100, + "example token", + 100, + "example token", + time.Now(), + nil, + 1023, + 1023, + nil, + }, + { + 100, + "example token", + 100, + "other token token", + time.Now(), + nil, + 1023, + -1, + errors.New("password_change_forbidden"), + }, + { + 100, + "example token", + 102, + "other example token", + time.Now(), + nil, + 1023, + -1, + errors.New("password_change_forbidden"), + }, + { + 100, + "example token", + 102, + "example token", + time.Now(), + nil, + 1023, + -1, + errors.New("password_change_forbidden"), + }, + { + 100, + "example token", + 100, + "example token", + time.Now(), + errors.New("queryError"), + 1023, + -1, + errors.New("password_change_forbidden"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) + + repo, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + rows := sqlmock.NewRows([]string{"id", "userId", "email", "token", "issued", "type"}). + AddRow(testCase.rowId, testCase.userIdDb, "email", testCase.tokenDb, testCase.issueTime, 1) + + dbMock.ExpectQuery("SELECT (.+) FROM " + passwords.TableName). + WillReturnRows(rows). + WillReturnError(testCase.queryError) + + // act + returnedRowId, err := repo.CheckForResetToken(testCase.userId, testCase.token) + + // assert + assert.Equal(t, testCase.expectedRowId, returnedRowId) + assert.Equal(t, testCase.expectedError, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_CheckForActivationToken(t *testing.T) { + testCases := []struct { + userId int + token string + userIdDb int + tokenDb string + issueTime time.Time + queryError error + rowId int + expectedRowId int + expectedError error + }{ + { + 100, + "example token", + 100, + "example token", + time.Now().Add(-25 * time.Hour), + nil, + 1023, + 0, + errors.New("token_expired"), + }, + { + 100, + "example token", + 100, + "example token", + time.Now(), + nil, + 1023, + 1023, + nil, + }, + { + 100, + "example token", + 100, + "other token token", + time.Now(), + nil, + 1023, + -1, + errors.New("activation_forbidden"), + }, + { + 100, + "example token", + 102, + "other example token", + time.Now(), + nil, + 1023, + -1, + errors.New("activation_forbidden"), + }, + { + 100, + "example token", + 102, + "example token", + time.Now(), + nil, + 1023, + -1, + errors.New("activation_forbidden"), + }, + { + 100, + "example token", + 100, + "example token", + time.Now(), + errors.New("queryError"), + 1023, + -1, + errors.New("activation_forbidden"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) + + repo, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + rows := sqlmock.NewRows([]string{"id", "userId", "email", "token", "issued", "type"}). + AddRow(testCase.rowId, testCase.userIdDb, "email", testCase.tokenDb, testCase.issueTime, 2) + + dbMock.ExpectQuery("SELECT (.+) FROM " + passwords.TableName). + WillReturnRows(rows). + WillReturnError(testCase.queryError) + + // act + returnedRowId, err := repo.CheckForActivationToken(testCase.userId, testCase.token) + + // assert + assert.Equal(t, testCase.expectedRowId, returnedRowId) + assert.Equal(t, testCase.expectedError, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_CheckForOneTimePasswordToken(t *testing.T) { + testCases := []struct { + userId int + token string + userIdDb int + tokenDb string + issueTime time.Time + queryError error + rowId int + expectedRowId int + expectedError error + }{ + { + 100, + "example token", + 100, + "example token", + time.Now().Add(-25 * time.Hour), + nil, + 1023, + 0, + errors.New("token_expired"), + }, + { + 100, + "example token", + 100, + "example token", + time.Now(), + nil, + 1023, + 1023, + nil, + }, + { + 100, + "example token", + 100, + "other token token", + time.Now(), + nil, + 1023, + -1, + errors.New("oneTimePassword_login_forbidden"), + }, + { + 100, + "example token", + 102, + "other example token", + time.Now(), + nil, + 1023, + -1, + errors.New("oneTimePassword_login_forbidden"), + }, + { + 100, + "example token", + 102, + "example token", + time.Now(), + nil, + 1023, + -1, + errors.New("oneTimePassword_login_forbidden"), + }, + { + 100, + "example token", + 100, + "example token", + time.Now(), + errors.New("queryError"), + 1023, + -1, + errors.New("oneTimePassword_login_forbidden"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) + + repo, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + rows := sqlmock.NewRows([]string{"id", "userId", "email", "token", "issued", "type"}). + AddRow(testCase.rowId, testCase.userIdDb, "email", testCase.tokenDb, testCase.issueTime, 3) + + dbMock.ExpectQuery("SELECT (.+) FROM " + passwords.TableName). + WillReturnRows(rows). + WillReturnError(testCase.queryError) + + // act + returnedRowId, err := repo.CheckForOneTimePasswordToken(testCase.userId, testCase.token) + + // assert + assert.Equal(t, testCase.expectedRowId, returnedRowId) + assert.Equal(t, testCase.expectedError, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_UseToken(t *testing.T) { + testCases := []struct { + tokenId int + error error + }{ + { + 100, + nil, + }, + { + 100, + errors.New("exampleError"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) + + repo, _ := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + dbMock.ExpectExec("delete FROM " + passwords.TableName). + WithArgs(testCase.tokenId). // one for activation + WillReturnResult(sqlmock.NewResult(0, 0)). + WillReturnError(testCase.error) + + // act + err := repo.UseToken(testCase.tokenId) + + // assert + assert.Equal(t, testCase.error, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_CleanUp(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + dbMock.ExpectPrepare("INSERT INTO " + passwords.TableName).WillBeClosed() + dbMock.ExpectPrepare("SELECT (.+) FROM " + passwords.TableName).WillBeClosed() + dbMock.ExpectPrepare("delete FROM " + passwords.TableName).WillBeClosed() + + mockEmailer := mocks.NewMockEmailer(mockCtrl) + mockConfiguration := mocks.NewMockConfiguration(mockCtrl) + mockConfiguration.EXPECT().GetStruct("password_reset", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "test email subject" + as.Template = "test email template" + }) + mockConfiguration.EXPECT().GetStruct("user_activation", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "test email subject" + as.Template = "test email template" + }) + mockConfiguration.EXPECT().GetStruct("one_time_password", gomock.Any()).Times(1).Do(func(name string, s interface{}) { + as, _ := s.(*passwords.PasswordResetConfig) + as.Subject = "one time email subject" + as.Template = "one time email template" + }) + mockConfiguration.EXPECT().GetFloat("tokenLifeSpan").Times(1).Return(float64(24), nil) + + repo, _ := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) + + // act + repo.CleanUp() + + // assert + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } +} diff --git a/preferences/Handlers.go b/preferences/handlers.go similarity index 69% rename from preferences/Handlers.go rename to preferences/handlers.go index 3f4f9ea..3f8b90a 100644 --- a/preferences/Handlers.go +++ b/preferences/handlers.go @@ -4,51 +4,43 @@ package preferences import ( - "github.com/reaction-eng/restlib/routing" - "github.com/reaction-eng/restlib/users" - "github.com/reaction-eng/restlib/utils" "encoding/json" + "errors" "io/ioutil" "net/http" + + "github.com/reaction-eng/restlib/routing" + "github.com/reaction-eng/restlib/users" + "github.com/reaction-eng/restlib/utils" ) -/** - * This struct is used - */ type Handler struct { // The user handler needs to have access to user repo userRepo users.Repo - //Store the repo for the roles - roleRepo Repo + prefRepo Repo } -/** - * This struct is used - */ -func NewHandler(userRepo users.Repo, roleRepo Repo) *Handler { +func NewHandler(userRepo users.Repo, prefRepo Repo) *Handler { //Build a new User Handler handler := Handler{ userRepo: userRepo, - roleRepo: roleRepo, + prefRepo: prefRepo, } return &handler } -/** -Function used to get routes -*/ func (handler *Handler) GetRoutes() []routing.Route { var routes = []routing.Route{ - { //Allow for the user to login + { Name: "Get the User Preferences", Method: "GET", Pattern: "/users/preferences", HandlerFunc: handler.handleUserPreferencesGet, }, - { //Allow for the user to login + { Name: "Set the User Preferences", Method: "POST", Pattern: "/users/preferences", @@ -60,12 +52,15 @@ func (handler *Handler) GetRoutes() []routing.Route { } -/** -*Get the current up to date user - */ func (handler *Handler) handleUserPreferencesGet(w http.ResponseWriter, r *http.Request) { //We have gone through the auth, so we should know the id of the logged in user + loggedInUserString := r.Context().Value("user") + if loggedInUserString == nil { + utils.ReturnJsonError(w, http.StatusForbidden, errors.New("no_user_logged_in")) + return + } + loggedInUser := r.Context().Value("user").(int) //Grab the id of the user that send the request //Get the user @@ -73,28 +68,29 @@ func (handler *Handler) handleUserPreferencesGet(w http.ResponseWriter, r *http. //If there is no error if err != nil { - utils.ReturnJsonError(w, http.StatusUnprocessableEntity, err) + utils.ReturnJsonError(w, http.StatusForbidden, err) return } - //Get the list of permissions - perf, err := handler.roleRepo.GetPreferences(user) + perf, err := handler.prefRepo.GetPreferences(user) //Check to see if the user was created if err == nil { utils.ReturnJson(w, http.StatusOK, perf) } else { - utils.ReturnJsonStatus(w, http.StatusUnsupportedMediaType, false, err.Error()) + utils.ReturnJsonStatus(w, http.StatusServiceUnavailable, false, err.Error()) } } -/** -*Get the current up to date user - */ func (handler *Handler) handleUserPreferencesSet(w http.ResponseWriter, r *http.Request) { //We have gone through the auth, so we should know the id of the logged in user + loggedInUserString := r.Context().Value("user") //Grab the id of the user that send the request + if loggedInUserString == nil { + utils.ReturnJsonError(w, http.StatusForbidden, errors.New("no_user_logged_in")) + return + } loggedInUser := r.Context().Value("user").(int) //Grab the id of the user that send the request //Get the user @@ -102,11 +98,10 @@ func (handler *Handler) handleUserPreferencesSet(w http.ResponseWriter, r *http. //If there is no error if err != nil { - utils.ReturnJsonError(w, http.StatusUnprocessableEntity, err) + utils.ReturnJsonError(w, http.StatusForbidden, err) return } - //Load in a limited amount of data from the body body, err := ioutil.ReadAll(r.Body) //Create an empty new calc @@ -114,18 +109,17 @@ func (handler *Handler) handleUserPreferencesSet(w http.ResponseWriter, r *http. //Now marshal it into the body if err := json.Unmarshal(body, &settings); err != nil { - utils.ReturnJsonStatus(w, http.StatusForbidden, false, err.Error()) + utils.ReturnJsonStatus(w, http.StatusBadRequest, false, err.Error()) return } - //Get the list of permissions - pref, err := handler.roleRepo.SetPreferences(user, &settings) + pref, err := handler.prefRepo.SetPreferences(user, &settings) //Check to see if the user was created if err == nil { utils.ReturnJson(w, http.StatusOK, pref) } else { - utils.ReturnJsonStatus(w, http.StatusUnsupportedMediaType, false, err.Error()) + utils.ReturnJsonStatus(w, http.StatusServiceUnavailable, false, err.Error()) } } diff --git a/preferences/handlers_test.go b/preferences/handlers_test.go new file mode 100644 index 0000000..0faa8d8 --- /dev/null +++ b/preferences/handlers_test.go @@ -0,0 +1,258 @@ +package preferences_test + +import ( + "errors" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/reaction-eng/restlib/users" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/reaction-eng/restlib/preferences" + "github.com/stretchr/testify/assert" +) + +func TestNewHandler(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockPrefRepo := mocks.NewMockPreferencesRepo(mockCtrl) + + // act + handler := preferences.NewHandler(mockUserRepo, mockPrefRepo) + + // assert + assert.NotNil(t, handler) +} + +func TestHandler_handleUserPreferencesGet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + user func() users.User + userError error + preferences *preferences.Preferences + preferencesError error + expectedStatus int + expectedResponse string + }{ + { + comment: "with user error", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + userError: errors.New("user_db_error"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"user_db_error\",\"status\":false}\n", + }, + { + comment: "with preferences error", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + preferencesError: errors.New("preference_error"), + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"preference_error\",\"status\":false}\n", + }, + { + comment: "with user and preferences errors", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + userError: errors.New("user_db_error"), + preferencesError: errors.New("preference_error"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"user_db_error\",\"status\":false}\n", + }, + { + comment: "all valid", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + preferences: &preferences.Preferences{Settings: &preferences.SettingGroup{Settings: map[string]string{"info": "123"}}}, + expectedStatus: http.StatusOK, + expectedResponse: "{\"settings\":{\"settings\":{\"info\":\"123\"},\"subgroup\":null},\"options\":null}\n", + }, + { + comment: "no user logged in", + user: func() users.User { return nil }, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"no_user_logged_in\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockUserRepo.EXPECT().GetUser(101).MaxTimes(1).Return(testCase.user(), testCase.userError) + + mockPrefRepo := mocks.NewMockPreferencesRepo(mockCtrl) + mockPrefRepo.EXPECT().GetPreferences(testCase.user()).MaxTimes(1).Return(testCase.preferences, testCase.preferencesError) + + handler := preferences.NewHandler(mockUserRepo, mockPrefRepo) + + router := mocks.NewTestRouterWithUser(handler, testCase.user(), 0) + + req := httptest.NewRequest("GET", "http://localhost/users/preferences", nil) + w := httptest.NewRecorder() + + // act + route := router.Handle(w, req) + + // assert + assert.NotNil(t, route) + assert.Equal(t, false, route.Public) + assert.Nil(t, route.ReqPermissions) + assert.Equal(t, testCase.expectedStatus, w.Result().StatusCode) + assert.Equal(t, testCase.expectedResponse, w.Body.String()) + } + +} + +func TestHandler_handleUserPreferencesSet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + user func() users.User + userError error + body io.Reader + preferences *preferences.Preferences + preferencesError error + expectedStatus int + expectedResponse string + }{ + { + comment: "no user logged in", + user: func() users.User { return nil }, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"no_user_logged_in\",\"status\":false}\n", + }, + + { + comment: "with user error", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + userError: errors.New("user_db_error"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"user_db_error\",\"status\":false}\n", + }, + { + comment: "with user and preference error", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + userError: errors.New("user_db_error"), + preferencesError: errors.New("preference_error"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"user_db_error\",\"status\":false}\n", + }, + { + comment: "with preference error and body", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + body: strings.NewReader("{\"info\":\"123\"}"), + preferencesError: errors.New("preference_error"), + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"preference_error\",\"status\":false}\n", + }, + { + comment: "without body", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + preferencesError: errors.New("preference_error"), + expectedStatus: http.StatusBadRequest, + expectedResponse: "{\"message\":\"unexpected end of JSON input\",\"status\":false}\n", + }, + { + comment: "with bad body", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + body: strings.NewReader("{\"settings\"null}"), + preferencesError: errors.New("preference_error"), + expectedStatus: http.StatusBadRequest, + expectedResponse: "{\"message\":\"invalid character 'n' after object key\",\"status\":false}\n", + }, + { + comment: "with user and preference error and body", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + body: strings.NewReader("{\"info\":\"123\"}"), + userError: errors.New("user_db_error"), + preferencesError: errors.New("preference_error"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"user_db_error\",\"status\":false}\n", + }, + { + comment: "all good", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).MaxTimes(1) + return user + }, + body: strings.NewReader("{\"info\":\"123\"}"), + preferences: &preferences.Preferences{Settings: &preferences.SettingGroup{Settings: map[string]string{"info": "123"}}}, + expectedStatus: http.StatusOK, + expectedResponse: "{\"settings\":{\"settings\":{\"info\":\"123\"},\"subgroup\":null},\"options\":null}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockUserRepo.EXPECT().GetUser(101).MaxTimes(1).Return(testCase.user(), testCase.userError) + + mockPrefRepo := mocks.NewMockPreferencesRepo(mockCtrl) + mockPrefRepo.EXPECT().SetPreferences(testCase.user(), gomock.Any()).MaxTimes(1).Return(testCase.preferences, testCase.preferencesError) + + handler := preferences.NewHandler(mockUserRepo, mockPrefRepo) + + router := mocks.NewTestRouterWithUser(handler, testCase.user(), 0) + + req := httptest.NewRequest("POST", "http://localhost/users/preferences", testCase.body) + w := httptest.NewRecorder() + + // act + route := router.Handle(w, req) + + // assert + assert.NotNil(t, route) + assert.Equal(t, false, route.Public) + assert.Nil(t, route.ReqPermissions) + assert.Equal(t, testCase.expectedStatus, w.Result().StatusCode) + assert.Equal(t, testCase.expectedResponse, w.Body.String()) + } + +} diff --git a/preferences/Option.go b/preferences/option.go similarity index 84% rename from preferences/Option.go rename to preferences/option.go index 41a3ab2..bf039d9 100644 --- a/preferences/Option.go +++ b/preferences/option.go @@ -5,7 +5,6 @@ package preferences import ( "encoding/json" - "log" "os" ) @@ -18,8 +17,8 @@ const ( Bool OptionType = "bool" ) -//Restore an optiosn group from a file -func LoadOptionsGroup(jsonFile string) *OptionGroup { +//Restore an options group from a file +func LoadOptionsGroup(jsonFile string) (*OptionGroup, error) { optGroup := &OptionGroup{} @@ -27,15 +26,19 @@ func LoadOptionsGroup(jsonFile string) *OptionGroup { configFileStream, err := os.Open(jsonFile) if err == nil { - //Get the json and add to the Params + //Get the json and add to the params jsonParser := json.NewDecoder(configFileStream) - jsonParser.Decode(&optGroup) + err = jsonParser.Decode(&optGroup) + if err != nil { + return nil, err + } + configFileStream.Close() } else { - log.Fatal(err) + return nil, err } - return optGroup + return optGroup, nil } type Option struct { diff --git a/preferences/option_test.go b/preferences/option_test.go new file mode 100644 index 0000000..36871ed --- /dev/null +++ b/preferences/option_test.go @@ -0,0 +1,175 @@ +package preferences_test + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/reaction-eng/restlib/preferences" + "github.com/stretchr/testify/assert" +) + +func TestLoadOptionsGroup(t *testing.T) { + // arrange + jsonString := `{ + "id": "root", + "name": "Den Preferences", + "description": "Use this section to set and update CAWS Den Preferences", + "options": [ + { + "id":"showIntroOnLaunch", + "hidden": true, + "type": "bool", + "defaultValue": "true" + }, + { + "id":"siteAgreement", + "hidden": true, + "type": "bool", + "defaultValue": "false" + } + ], + + "subgroups": [ + { + "id": "communication", + "name":"Communication", + "Description": "Control how the CAWS Den communicates with you.", + "Options":[ + { + "id": "emailNews", + "name":"Email News & Updates", + "description": "Should the CAWS Den email you News & Updates when posted?", + "type": "bool", + "defaultValue": "true" + }, + { + "id": "inneedEmail", + "name":"In-Need of Foster Email Frequency", + "description": "How often should the CAWS Den email you about animals in-need of foster?", + "type": "string", + "defaultValue": "weekly", + "selection": ["daily", "weekly", "monthly", "never"] + }, + { + "id": "inneedSpecies", + "name":"In-Need of Foster Email Species", + "description": "Would you like to receive in-need emails about Cats, Dogs, or both?", + "type": "string", + "defaultValue": "Cat,Dog", + "selection": ["Cat,Dog", "Dog", "Cat"] + } + ] + } + ] + }` + + file, err := ioutil.TempFile(os.TempDir(), "optionTest-") + assert.Nil(t, err) + defer os.Remove(file.Name()) + + file.WriteString(jsonString) + file.Close() + + // act + optionsGroup, err := preferences.LoadOptionsGroup(file.Name()) + + // assert + assert.Nil(t, err) + assert.NotNil(t, optionsGroup) + assert.Equal(t, "root", optionsGroup.Id) + assert.Equal(t, "Use this section to set and update CAWS Den Preferences", optionsGroup.Description) + assert.Equal(t, "Den Preferences", optionsGroup.Name) + + assert.Equal(t, 2, len(optionsGroup.Options)) + assert.Equal(t, "", optionsGroup.Options[0].Name) + assert.Equal(t, "", optionsGroup.Options[0].Description) + assert.Equal(t, "showIntroOnLaunch", optionsGroup.Options[0].Id) + assert.Equal(t, "true", optionsGroup.Options[0].DefaultValue) + assert.Equal(t, true, optionsGroup.Options[0].Hidden) + assert.Equal(t, float64(0), optionsGroup.Options[0].MaxValue) + assert.Equal(t, float64(0), optionsGroup.Options[0].MinValue) + assert.Nil(t, optionsGroup.Options[0].Selection) + assert.Equal(t, preferences.Bool, optionsGroup.Options[0].Type) + + assert.Equal(t, "", optionsGroup.Options[1].Name) + assert.Equal(t, "", optionsGroup.Options[1].Description) + assert.Equal(t, "siteAgreement", optionsGroup.Options[1].Id) + assert.Equal(t, "false", optionsGroup.Options[1].DefaultValue) + assert.Equal(t, true, optionsGroup.Options[1].Hidden) + assert.Equal(t, float64(0), optionsGroup.Options[1].MaxValue) + assert.Equal(t, float64(0), optionsGroup.Options[1].MinValue) + assert.Nil(t, optionsGroup.Options[1].Selection) + assert.Equal(t, preferences.Bool, optionsGroup.Options[1].Type) + + assert.Equal(t, 1, len(optionsGroup.SubGroups)) + assert.Equal(t, "communication", optionsGroup.SubGroups[0].Id) + assert.Equal(t, "Communication", optionsGroup.SubGroups[0].Name) + assert.Equal(t, "Control how the CAWS Den communicates with you.", optionsGroup.SubGroups[0].Description) + assert.Nil(t, optionsGroup.SubGroups[0].SubGroups) + + assert.Equal(t, 3, len(optionsGroup.SubGroups[0].Options)) + assert.Equal(t, "Email News & Updates", optionsGroup.SubGroups[0].Options[0].Name) + assert.Equal(t, "Should the CAWS Den email you News & Updates when posted?", optionsGroup.SubGroups[0].Options[0].Description) + assert.Equal(t, "emailNews", optionsGroup.SubGroups[0].Options[0].Id) + assert.Equal(t, "true", optionsGroup.SubGroups[0].Options[0].DefaultValue) + assert.Equal(t, false, optionsGroup.SubGroups[0].Options[0].Hidden) + assert.Equal(t, float64(0), optionsGroup.SubGroups[0].Options[0].MaxValue) + assert.Equal(t, float64(0), optionsGroup.SubGroups[0].Options[0].MinValue) + assert.Nil(t, optionsGroup.SubGroups[0].Options[0].Selection) + assert.Equal(t, preferences.Bool, optionsGroup.SubGroups[0].Options[0].Type) + + assert.Equal(t, "In-Need of Foster Email Frequency", optionsGroup.SubGroups[0].Options[1].Name) + assert.Equal(t, "How often should the CAWS Den email you about animals in-need of foster?", optionsGroup.SubGroups[0].Options[1].Description) + assert.Equal(t, "inneedEmail", optionsGroup.SubGroups[0].Options[1].Id) + assert.Equal(t, "weekly", optionsGroup.SubGroups[0].Options[1].DefaultValue) + assert.Equal(t, false, optionsGroup.SubGroups[0].Options[1].Hidden) + assert.Equal(t, float64(0), optionsGroup.SubGroups[0].Options[1].MaxValue) + assert.Equal(t, float64(0), optionsGroup.SubGroups[0].Options[1].MinValue) + assert.Equal(t, []string{"daily", "weekly", "monthly", "never"}, optionsGroup.SubGroups[0].Options[1].Selection) + assert.Equal(t, preferences.String, optionsGroup.SubGroups[0].Options[1].Type) + + assert.Equal(t, "In-Need of Foster Email Species", optionsGroup.SubGroups[0].Options[2].Name) + assert.Equal(t, "Would you like to receive in-need emails about Cats, Dogs, or both?", optionsGroup.SubGroups[0].Options[2].Description) + assert.Equal(t, "inneedSpecies", optionsGroup.SubGroups[0].Options[2].Id) + assert.Equal(t, "Cat,Dog", optionsGroup.SubGroups[0].Options[2].DefaultValue) + assert.Equal(t, false, optionsGroup.SubGroups[0].Options[2].Hidden) + assert.Equal(t, float64(0), optionsGroup.SubGroups[0].Options[2].MaxValue) + assert.Equal(t, float64(0), optionsGroup.SubGroups[0].Options[2].MinValue) + assert.Equal(t, []string{"Cat,Dog", "Dog", "Cat"}, optionsGroup.SubGroups[0].Options[2].Selection) + assert.Equal(t, preferences.String, optionsGroup.SubGroups[0].Options[2].Type) +} + +func TestLoadOptionsGroup_ThrowsErrorForMissingFile(t *testing.T) { + // arrange + file := "fake/file.json" + // act + optionsGroup, err := preferences.LoadOptionsGroup(file) + + // assert + assert.NotNil(t, err) + assert.Nil(t, optionsGroup) +} + +func TestLoadOptionsGroup_ThrowsErrorForBadJson(t *testing.T) { + // arrange + jsonString := `{ + "id": "root", + "name": "Den Preferences", + "description": "Use this section to set and update CAWS Den Preferences", + "options": [ + }` + file, err := ioutil.TempFile(os.TempDir(), "optionTest-") + assert.Nil(t, err) + defer os.Remove(file.Name()) + + file.WriteString(jsonString) + file.Close() + + // act + optionsGroup, err := preferences.LoadOptionsGroup(file.Name()) + + // assert + assert.NotNil(t, err) + assert.Nil(t, optionsGroup) +} diff --git a/preferences/Preferences.go b/preferences/preferences.go similarity index 100% rename from preferences/Preferences.go rename to preferences/preferences.go diff --git a/preferences/Repo.go b/preferences/repo.go similarity index 75% rename from preferences/Repo.go rename to preferences/repo.go index 5b52109..b5d6c7b 100644 --- a/preferences/Repo.go +++ b/preferences/repo.go @@ -3,13 +3,12 @@ package preferences +//go:generate mockgen -destination=../mocks/mock_preferencesRepo.go -package=mocks -mock_names Repo=MockPreferencesRepo github.com/reaction-eng/restlib/preferences Repo + import ( "github.com/reaction-eng/restlib/users" ) -/** -Define an interface for roles -*/ type Repo interface { /** Get the preferences for this repo diff --git a/preferences/RepoSql.go b/preferences/repoSql.go similarity index 63% rename from preferences/RepoSql.go rename to preferences/repoSql.go index 834c604..8b621ba 100644 --- a/preferences/RepoSql.go +++ b/preferences/repoSql.go @@ -4,14 +4,13 @@ package preferences import ( - "github.com/reaction-eng/restlib/users" "database/sql" - "log" + + "github.com/reaction-eng/restlib/users" ) -/** -Define a struct for Repo for use with users -*/ +const TableName = "userpref" + type RepoSql struct { //Hold on to the sql databased db *sql.DB @@ -27,85 +26,55 @@ type RepoSql struct { baseOptions *OptionGroup } -//Provide a method to make a new UserRepoSql -func NewRepoMySql(db *sql.DB, tableName string, baseOptions *OptionGroup) *RepoSql { - +func NewRepoMySql(db *sql.DB, baseOptions *OptionGroup) (*RepoSql, error) { //Define a new repo newRepo := RepoSql{ db: db, - tableName: tableName, baseOptions: baseOptions, } - //Create the table if it is not already there - //Create a table - _, err := db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(userId int NOT NULL, settings TEXT NOT NULL, PRIMARY KEY (userId) )") - if err != nil { - log.Fatal(err) - } - - //Get the settings - getSetting, err := db.Prepare("SELECT settings FROM " + tableName + " WHERE userID = ?") + getSetting, err := db.Prepare("SELECT settings FROM " + TableName + " WHERE userID = ?") //Check for error if err != nil { - log.Fatal(err) + return nil, err } newRepo.getSettingFromDbCmd = getSetting - //Get the settings - setSetting, err := db.Prepare("INSERT INTO " + tableName + "(userId,settings) VALUES (?,?) ON DUPLICATE KEY UPDATE settings = VALUES(settings)") + setSetting, err := db.Prepare("INSERT INTO " + TableName + "(userId,settings) VALUES (?,?) ON DUPLICATE KEY UPDATE settings = VALUES(settings)") //Check for error if err != nil { - log.Fatal(err) + return nil, err } newRepo.setSettingIntoDbCmd = setSetting - //Return a point - return &newRepo - + return &newRepo, nil } -//Provide a method to make a new UserRepoSql -func NewRepoPostgresSql(db *sql.DB, tableName string, baseOptions *OptionGroup) *RepoSql { - +func NewRepoPostgresSql(db *sql.DB, baseOptions *OptionGroup) (*RepoSql, error) { //Define a new repo newRepo := RepoSql{ db: db, - tableName: tableName, baseOptions: baseOptions, } - //Create the table if it is not already there - //Create a table - _, err := db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(userId SERIAL PRIMARY KEY, settings TEXT NOT NULL)") - if err != nil { - log.Fatal(err) - } - - //Get the settings - getSetting, err := db.Prepare("SELECT settings FROM " + tableName + " WHERE userID = $1") + getSetting, err := db.Prepare("SELECT settings FROM " + TableName + " WHERE userID = $1") //Check for error if err != nil { - log.Fatal(err) + return nil, err } newRepo.getSettingFromDbCmd = getSetting //Get the settings - setSetting, err := db.Prepare("INSERT INTO " + tableName + "(userId,settings) VALUES ($1, $2) ON CONFLICT (userId) DO UPDATE SET settings = $2") + setSetting, err := db.Prepare("INSERT INTO " + TableName + "(userId,settings) VALUES ($1, $2) ON CONFLICT (userId) DO UPDATE SET settings = $2") //Check for error if err != nil { - log.Fatal(err) + return nil, err } newRepo.setSettingIntoDbCmd = setSetting - //Return a point - return &newRepo - + return &newRepo, nil } -/** -Get the user with the email. An error is thrown is not found -*/ func (repo *RepoSql) GetPreferences(user users.User) (*Preferences, error) { //Get the settings from the db settings, err := repo.getSettingsFromDb(user) @@ -126,9 +95,6 @@ func (repo *RepoSql) GetPreferences(user users.User) (*Preferences, error) { } -/** -Get the user with the email. An error is thrown is not found -*/ func (repo *RepoSql) getSettingsFromDb(user users.User) (*SettingGroup, error) { //Get the id var setting *SettingGroup @@ -158,11 +124,8 @@ func (repo *RepoSql) SetPreferences(user users.User, userSetting *SettingGroup) } -/** -Nothing much to do for the clean up -*/ func (repo *RepoSql) CleanUp() { //Close all of the prepared statements repo.getSettingFromDbCmd.Close() - + repo.setSettingIntoDbCmd.Close() } diff --git a/preferences/repoSql_test.go b/preferences/repoSql_test.go new file mode 100644 index 0000000..700c76d --- /dev/null +++ b/preferences/repoSql_test.go @@ -0,0 +1,260 @@ +package preferences_test + +import ( + "database/sql" + "errors" + "testing" + + "github.com/reaction-eng/restlib/mocks" + + "github.com/reaction-eng/restlib/preferences" + + "github.com/stretchr/testify/assert" + + "github.com/golang/mock/gomock" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestNewRepoMySql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("SELECT settings FROM " + preferences.TableName) + mock.ExpectPrepare("INSERT INTO " + preferences.TableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + optionsGroup := &preferences.OptionGroup{} + + // act + repoMySql, err := preferences.NewRepoMySql(db, optionsGroup) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func TestNewRepoPostgresSql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("SELECT settings FROM " + preferences.TableName) + mock.ExpectPrepare("INSERT INTO " + preferences.TableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + optionsGroup := &preferences.OptionGroup{} + + // act + repoMySql, err := preferences.NewRepoPostgresSql(db, optionsGroup) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func setupSqlMock(t *testing.T, mockCtrl *gomock.Controller) (*sql.DB, sqlmock.Sqlmock) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + mock.ExpectPrepare("SELECT settings FROM " + preferences.TableName) + mock.ExpectPrepare("INSERT INTO " + preferences.TableName) + + return db, mock +} + +func TestRepoSql_GetPreferences(t *testing.T) { + optionsGroup := &preferences.OptionGroup{ + Options: []preferences.Option{ + { + Id: "id1", + DefaultValue: "33", + }, + { + Id: "id2", + }, + }, + } + + testCases := []struct { + comment string + settingString string + queryError error + expectedPreferences *preferences.Preferences + expectedError error + }{ + { + comment: "working settings and no errors", + settingString: `{"settings":{"id1":"true","id2":"blue"}}`, + queryError: nil, + expectedPreferences: &preferences.Preferences{ + Options: optionsGroup, + Settings: &preferences.SettingGroup{ + Settings: map[string]string{"id1": "true", "id2": "blue"}, + SubGroup: map[string]*preferences.SettingGroup{}, + }, + }, + expectedError: nil, + }, { + comment: "no rows, returns default values", + settingString: ``, + queryError: sql.ErrNoRows, + expectedPreferences: &preferences.Preferences{ + Options: optionsGroup, + Settings: &preferences.SettingGroup{ + Settings: map[string]string{"id1": "33", "id2": ""}, + SubGroup: map[string]*preferences.SettingGroup{}, + }, + }, + expectedError: nil, + }, + { + comment: "db error, pass along", + settingString: ``, + queryError: errors.New("new db error"), + expectedPreferences: nil, + expectedError: errors.New("new db error"), + }, + { + comment: "should repair missing parameters", + settingString: `{"settings":{"id2":"blue"}}`, + queryError: nil, + expectedPreferences: &preferences.Preferences{ + Options: optionsGroup, + Settings: &preferences.SettingGroup{ + Settings: map[string]string{"id1": "33", "id2": "blue"}, + SubGroup: map[string]*preferences.SettingGroup{}, + }, + }, + expectedError: nil, + }, + { + comment: "should throw error for bad json", + settingString: `{{`, + queryError: nil, + expectedPreferences: nil, + expectedError: errors.New(`sql: Scan error on column index 0, name "setting": invalid character '{' looking for beginning of object key string`), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t, mockCtrl) + + mockUser := mocks.NewMockUser(mockCtrl) + mockUser.EXPECT().Id().Return(3).MinTimes(1) + + var rows *sqlmock.Rows + if len(testCase.settingString) > 0 { + rows = sqlmock.NewRows([]string{"setting"}). + AddRow(testCase.settingString) + } + + dbMock.ExpectQuery("SELECT settings FROM " + preferences.TableName). + WithArgs(mockUser.Id()). + WillReturnRows(rows). + WillReturnError(testCase.queryError) + + repo, err := preferences.NewRepoPostgresSql(db, optionsGroup) + assert.Nil(t, err) + + // act + preference, err := repo.GetPreferences(mockUser) + assert.Equal(t, testCase.expectedError, err) + + // assert + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + assert.Equal(t, testCase.expectedPreferences, preference) + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestRepoSql_SetPreferences(t *testing.T) { + optionsGroup := &preferences.OptionGroup{} + + testCases := []struct { + comment string + expectedPreferences *preferences.Preferences + execError error + expectedError error + }{ + { + comment: "working settings and no errors", + expectedPreferences: &preferences.Preferences{ + Options: optionsGroup, + Settings: &preferences.SettingGroup{ + Settings: map[string]string{"id1": "true", "id2": "blue"}, + SubGroup: map[string]*preferences.SettingGroup{}, + }, + }, + execError: nil, + expectedError: nil, + }, + { + comment: "with error", + expectedPreferences: &preferences.Preferences{ + Options: optionsGroup, + Settings: &preferences.SettingGroup{ + Settings: map[string]string{"id1": "true", "id2": "blue"}, + SubGroup: map[string]*preferences.SettingGroup{}, + }, + }, + execError: errors.New("error from db set"), + expectedError: errors.New("error from db set"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t, mockCtrl) + + mockUser := mocks.NewMockUser(mockCtrl) + mockUser.EXPECT().Id().Return(3).MinTimes(1) + + dbMock.ExpectExec("INSERT INTO "+preferences.TableName). + WithArgs(mockUser.Id(), testCase.expectedPreferences.Settings). + WillReturnResult(sqlmock.NewResult(0, 0)). + WillReturnError(testCase.execError) + + repo, err := preferences.NewRepoPostgresSql(db, optionsGroup) + assert.Nil(t, err) + + // act + preference, err := repo.SetPreferences(mockUser, testCase.expectedPreferences.Settings) + + // assert + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + assert.Equal(t, testCase.expectedPreferences, preference) + assert.Equal(t, testCase.expectedError, err) + + // cleanup + db.Close() + mockCtrl.Finish() + } +} diff --git a/preferences/Setting.go b/preferences/setting.go similarity index 94% rename from preferences/Setting.go rename to preferences/setting.go index c6b2c0d..2c2a280 100644 --- a/preferences/Setting.go +++ b/preferences/setting.go @@ -10,12 +10,9 @@ import ( "strconv" ) -//Get the setting group type SettingGroup struct { - //And the value Settings map[string]string `json:"settings"` - //We can also old other groups SubGroup map[string]*SettingGroup `json:"subgroup"` } @@ -55,7 +52,6 @@ func (setGroup *SettingGroup) GetSubGroup(id string) *SettingGroup { } } -//Provide a way to get the sub group func (setGroup *SettingGroup) GetValueAsString(id string) (string, error) { //See if there is a settings group if group, found := setGroup.Settings[id]; found { @@ -65,7 +61,6 @@ func (setGroup *SettingGroup) GetValueAsString(id string) (string, error) { } } -//Provide a way to get the sub group func (setGroup *SettingGroup) GetValueAsBool(id string) (bool, error) { //Get the value value, err := setGroup.GetValueAsString(id) @@ -74,12 +69,11 @@ func (setGroup *SettingGroup) GetValueAsBool(id string) (bool, error) { } //Now convert to bool - valueBool, err := strconv.ParseBool(value) + valueBool, _ := strconv.ParseBool(value) return valueBool, nil } -//Provide a way to get the sub group func (setGroup *SettingGroup) GetSettingAsBool(tree []string) (bool, error) { //Start with the current subGroup subGroup := setGroup @@ -102,7 +96,6 @@ func (setGroup *SettingGroup) GetSettingAsString(tree []string) (string, error) //March down the list of subgroups until we get the tree for i := 0; i < len(tree)-1; i++ { subGroup = subGroup.GetSubGroup(tree[i]) - } //Now get the last value diff --git a/preferences/setting_test.go b/preferences/setting_test.go new file mode 100644 index 0000000..4fcda12 --- /dev/null +++ b/preferences/setting_test.go @@ -0,0 +1,488 @@ +package preferences + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewSettingGroup(t *testing.T) { + // arrange + // act + settingGroup := newSettingGroup() + + // assert + assert.NotNil(t, settingGroup) + assert.NotNil(t, settingGroup.Settings) + assert.NotNil(t, settingGroup.SubGroup) + assert.Empty(t, settingGroup.Settings) + assert.Empty(t, settingGroup.SubGroup) +} + +func TestCheckSubStructureValid(t *testing.T) { + // arrange + settingGroup := &SettingGroup{} + + // act + settingGroup.checkSubStructureValid() + + // assert + assert.NotNil(t, settingGroup) + assert.NotNil(t, settingGroup.Settings) + assert.NotNil(t, settingGroup.SubGroup) + assert.Empty(t, settingGroup.Settings) + assert.Empty(t, settingGroup.SubGroup) +} + +func TestGetSubGroup(t *testing.T) { + testCases := []struct { + currentSettingGroup *SettingGroup + expectedSubGroup *SettingGroup + }{ + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedSubGroup: &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{}, + }, + expectedSubGroup: newSettingGroup(), + }, + } + for _, testCase := range testCases { + // arrange + // act + result := testCase.currentSettingGroup.GetSubGroup("green") + + // assert + assert.Equal(t, testCase.expectedSubGroup, result) + } +} + +func TestGetValueAsString(t *testing.T) { + id := "blue" + + testCases := []struct { + currentSettingGroup *SettingGroup + expectedValue string + expectedError error + }{ + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: errors.New("setting " + id + " not found"), + expectedValue: "", + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + id: "123", + "alpha": "true", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: nil, + expectedValue: "123", + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + id: "123", + "alpha": "true", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "id": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: nil, + expectedValue: "123", + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "alpha": "true", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "id": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: errors.New("setting " + id + " not found"), + expectedValue: "", + }, + } + for _, testCase := range testCases { + // arrange + // act + result, err := testCase.currentSettingGroup.GetValueAsString(id) + + // assert + assert.Equal(t, testCase.expectedValue, result) + assert.Equal(t, testCase.expectedError, err) + } +} + +func TestGetValueAsBool(t *testing.T) { + id := "blue" + + testCases := []struct { + currentSettingGroup *SettingGroup + expectedValue bool + expectedError error + }{ + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: errors.New("setting " + id + " not found"), + expectedValue: false, + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + id: "123", + "alpha": "true", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: nil, + expectedValue: false, + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + id: "true", + "alpha": "true", + }, + }, + expectedError: nil, + expectedValue: true, + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + id: "false", + "alpha": "true", + }, + }, + expectedError: nil, + expectedValue: false, + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + id: "True", + "alpha": "true", + }, + }, + expectedError: nil, + expectedValue: true, + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + id: "False", + "alpha": "true", + }, + }, + expectedError: nil, + expectedValue: false, + }, + { + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "alpha": "true", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "id": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: errors.New("setting " + id + " not found"), + expectedValue: false, + }, + } + for _, testCase := range testCases { + // arrange + // act + result, err := testCase.currentSettingGroup.GetValueAsBool(id) + + // assert + assert.Equal(t, testCase.expectedValue, result) + assert.Equal(t, testCase.expectedError, err) + } +} + +func TestGetSettingAsString(t *testing.T) { + testCases := []struct { + tree []string + currentSettingGroup *SettingGroup + expectedValue string + expectedError error + }{ + { + tree: []string{"green", "blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: errors.New("setting blue not found"), + expectedValue: "", + }, + { + tree: []string{"green", "blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + "blue": "1232", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: nil, + expectedValue: "1232", + }, + { + tree: []string{"blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "blue": "1232", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: nil, + expectedValue: "1232", + }, + { + tree: []string{"green", "green", "blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "blue": "1232", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "blue": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + }, + }, + expectedError: nil, + expectedValue: "yellow", + }, + { + tree: []string{"blue", "green", "yellow"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "blue": "1232", + }, + SubGroup: map[string]*SettingGroup{}, + }, + expectedError: errors.New("setting yellow not found"), + expectedValue: "", + }, + } + for _, testCase := range testCases { + // arrange + // act + result, err := testCase.currentSettingGroup.GetSettingAsString(testCase.tree) + + // assert + assert.Equal(t, testCase.expectedValue, result) + assert.Equal(t, testCase.expectedError, err) + } +} + +func TestGetSettingAsBool(t *testing.T) { + testCases := []struct { + tree []string + currentSettingGroup *SettingGroup + expectedValue bool + expectedError error + }{ + { + tree: []string{"green", "blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: errors.New("setting blue not found"), + expectedValue: false, + }, + { + tree: []string{"green", "blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{}, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + "blue": "true", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: nil, + expectedValue: true, + }, + { + tree: []string{"blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "blue": "true", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + expectedError: nil, + expectedValue: true, + }, + { + tree: []string{"green", "green", "blue"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "blue": "1232", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "red": "yellow", + }, + SubGroup: map[string]*SettingGroup{ + "green": &SettingGroup{ + Settings: map[string]string{ + "blue": "false", + }, + SubGroup: map[string]*SettingGroup{}, + }, + }, + }, + }, + }, + expectedError: nil, + expectedValue: false, + }, + { + tree: []string{"blue", "green", "yellow"}, + currentSettingGroup: &SettingGroup{ + Settings: map[string]string{ + "blue": "1232", + }, + SubGroup: map[string]*SettingGroup{}, + }, + expectedError: errors.New("setting yellow not found"), + expectedValue: false, + }, + } + for _, testCase := range testCases { + // arrange + // act + result, err := testCase.currentSettingGroup.GetSettingAsBool(testCase.tree) + + // assert + assert.Equal(t, testCase.expectedValue, result) + assert.Equal(t, testCase.expectedError, err) + } +} diff --git a/roles/Repo.go b/roles/Repo.go deleted file mode 100644 index f50b016..0000000 --- a/roles/Repo.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019 Reaction Engineering International. All rights reserved. -// Use of this source code is governed by the MIT license in the file LICENSE.txt. - -package roles - -import "github.com/reaction-eng/restlib/users" - -/** -Define an interface for roles -*/ -type Repo interface { - /** - Get the user with the email. An error is thrown is not found - */ - GetPermissions(user users.User) (*Permissions, error) - - /** - Set the user's roles. Note this wipes out all current roles - */ - SetRolesByRoleId(user users.User, roles []int) error - - /** - Set the user's roles. Note this wipes out all current roles - */ - SetRolesByName(user users.User, roles []string) error -} diff --git a/roles/RepoSql.go b/roles/RepoSql.go deleted file mode 100644 index 19a3ada..0000000 --- a/roles/RepoSql.go +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2019 Reaction Engineering International. All rights reserved. -// Use of this source code is governed by the MIT license in the file LICENSE.txt. - -package roles - -import ( - "github.com/reaction-eng/restlib/users" - "database/sql" - "log" -) - -/** -Define a struct for Repo for use with users -*/ -type RepoSql struct { - //Hold on to the sql databased - db *sql.DB - - //Also store the table name - tableName string - - //Store the required statements to reduce comput time - getUserRoles *sql.Stmt - clearUserRoles *sql.Stmt - addUserRole *sql.Stmt - - //We need the role Repo - permTable PermissionTable -} - -//Provide a method to make a new UserRepoSql -func NewRepoMySql(db *sql.DB, tableName string, roleRepo PermissionTable) *RepoSql { - - //Define a new repo - newRepo := RepoSql{ - db: db, - tableName: tableName, - permTable: roleRepo, - } - - //Create the table if it is not already there - //Create a table - _, err := db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(id int NOT NULL AUTO_INCREMENT, userId int, roleId int, PRIMARY KEY (id) )") - if err != nil { - log.Fatal(err) - } - - //Add calc data to table - getRoles, err := db.Prepare("SELECT roleId FROM " + tableName + " WHERE userId = ? ") - //Check for error - if err != nil { - log.Fatal(err) - } - newRepo.getUserRoles = getRoles - - //Clear all roles of a user - clearRoles, err := db.Prepare("DELETE FROM " + tableName + " WHERE userId = ? ") - //Check for error - if err != nil { - log.Fatal(err) - } - newRepo.clearUserRoles = clearRoles - - //Clear all roles of a user - addRole, err := db.Prepare("INSERT INTO " + tableName + "(userId,roleId) VALUES (?, ?)") - //Check for error - if err != nil { - log.Fatal(err) - } - newRepo.addUserRole = addRole - - //Return a point - return &newRepo - -} - -//Provide a method to make a new UserRepoSql -func NewRepoPostgresSql(db *sql.DB, tableName string, roleRepo PermissionTable) *RepoSql { - - //Define a new repo - newRepo := RepoSql{ - db: db, - tableName: tableName, - permTable: roleRepo, - } - - //Create the table if it is not already there - //Create a table - _, err := db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(id SERIAL PRIMARY KEY, userId int NOT NULL, roleId int NOT NULL )") - if err != nil { - log.Fatal(err) - } - - //Add calc data to table - getRoles, err := db.Prepare("SELECT roleId FROM " + tableName + " WHERE userId = $1 ") - //Check for error - if err != nil { - log.Fatal(err) - } - newRepo.getUserRoles = getRoles - - //Clear all roles of a user - clearRoles, err := db.Prepare("DELETE FROM " + tableName + " WHERE userId = $1 ") - //Check for error - if err != nil { - log.Fatal(err) - } - newRepo.clearUserRoles = clearRoles - - //Clear all roles of a user - addRole, err := db.Prepare("INSERT INTO " + tableName + "(userId,roleId) VALUES ($1, $2)") - //Check for error - if err != nil { - log.Fatal(err) - } - newRepo.addUserRole = addRole - - //Return a point - return &newRepo - -} - -/** -Get the user with the email. An error is thrown is not found -*/ -func (repo *RepoSql) GetPermissions(user users.User) (*Permissions, error) { - //Get a list of roles - permissions := make([]string, 0) - - //Get the value //id int NOT NULL AUTO_INCREMENT, email TEXT, password TEXT, PRIMARY KEY (id) - rows, err := repo.getUserRoles.Query(user.Id()) - - //Rows is the result of a query. Its cursor starts before the first row of the result set. Use Next to advance through the rows: - defer rows.Close() - for rows.Next() { - //Get the role id - var roleId int - err = rows.Scan(&roleId) - - //Get the permissions - rolePermissions := repo.permTable.GetPermissions(roleId) - - //Push back - permissions = append(permissions, rolePermissions...) - - } - rows.Close() - err = rows.Err() // get any error encountered ing iteration - - //If there is an error - if err != nil { - return nil, err - } - - //Get the permissions from - return &Permissions{ - Permissions: permissions, - }, nil - -} - -/** -Get all of the roles -*/ -func (repo *RepoSql) GetRoleIds(user users.User) ([]int, error) { - //Get a list of roles - roles := make([]int, 0) - - //Get the value //id int NOT NULL AUTO_INCREMENT, email TEXT, password TEXT, PRIMARY KEY (id) - rows, err := repo.getUserRoles.Query(user.Id()) - - //Build the list - - //Rows is the result of a query. Its cursor starts before the first row of the result set. Use Next to advance through the rows: - defer rows.Close() - for rows.Next() { - //Get the role id - var roleId int - err = rows.Scan(&roleId) - - //Push back - roles = append(roles, roleId) - - } - rows.Close() - err = rows.Err() // get any error encountered ing iteration - - //If there is an error - if err != nil { - return nil, err - } - - return roles, nil -} - -/** -Get the user with the email. An error is thrown is not found -*/ -func (repo *RepoSql) SetRolesByRoleId(user users.User, roles []int) error { - //Get all of the - currentRoles, err := repo.GetRoleIds(user) - - //If the roles dont' equal replace them - if err != nil || !sameRoles(currentRoles, roles) { - - //Clear all of the roles - _, err := repo.clearUserRoles.Exec(user.Id()) - - //Now add each role - for _, roleId := range roles { - _, err = repo.addUserRole.Exec(user.Id(), roleId) - } - - //Check for error - return err - } - return nil -} - -/** -Set the user's roles. Note this wipes out all current roles -*/ -func (repo *RepoSql) SetRolesByName(user users.User, roles []string) error { - //Build a list of roles to add - roleIds := make([]int, 0) - - //March over the roles - for _, role := range roles { - //Get the id - roleId, err := repo.permTable.LookUpRoleId(role) - - //Add it to the list - if err == nil { - roleIds = append(roleIds, roleId) - } - } - - //Now update the roles - return repo.SetRolesByRoleId(user, roleIds) -} - -/** -Clean up the database, nothing much to do -*/ -func (repo *RepoSql) CleanUp() { - repo.getUserRoles.Close() - -} - -//See if the roles are the same -func sameRoles(a, b []int) bool { - - // If one is nil, the other must also be nil. - if (a == nil) != (b == nil) { - return false - } - - if len(a) != len(b) { - return false - } - - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true -} - -//func RepoDestroyCalc(id int) error { -// for i, t := range usersList { -// if t.Id == id { -// usersList = append(usersList[:i], usersList[i+1:]...) -// return nil -// } -// } -// return fmt.Errorf("Could not find Todo with id of %d to delete", id) -//} diff --git a/roles/Documentation.go b/roles/documentation.go similarity index 100% rename from roles/Documentation.go rename to roles/documentation.go diff --git a/roles/Handlers.go b/roles/handlers.go similarity index 82% rename from roles/Handlers.go rename to roles/handlers.go index 295f468..8ef6f92 100644 --- a/roles/Handlers.go +++ b/roles/handlers.go @@ -4,15 +4,13 @@ package roles import ( + "net/http" + "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/users" "github.com/reaction-eng/restlib/utils" - "net/http" ) -/** - * This struct is used - */ type Handler struct { // The user handler needs to have access to user repo userRepo users.Repo @@ -21,9 +19,6 @@ type Handler struct { roleRepo Repo } -/** - * This struct is used - */ func NewHandler(userRepo users.Repo, roleRepo Repo) *Handler { //Build a new User Handler handler := Handler{ @@ -34,9 +29,6 @@ func NewHandler(userRepo users.Repo, roleRepo Repo) *Handler { return &handler } -/** -Function used to get routes -*/ func (handler *Handler) GetRoutes() []routing.Route { var routes = []routing.Route{ @@ -59,13 +51,14 @@ func (handler *Handler) GetRoutes() []routing.Route { } -/** -*Get the current up to date user - */ func (handler *Handler) handleUserPermissionsGet(w http.ResponseWriter, r *http.Request) { //We have gone through the auth, so we should know the id of the logged in user - loggedInUser := r.Context().Value("user").(int) //Grab the id of the user that send the request + loggedInUser, organizationId, err := utils.UserFromContext(r.Context()) + if err != nil { + utils.ReturnJsonError(w, http.StatusForbidden, err) + return + } //Get the user user, err := handler.userRepo.GetUser(loggedInUser) @@ -77,13 +70,12 @@ func (handler *Handler) handleUserPermissionsGet(w http.ResponseWriter, r *http. } //Get the list of permissions - perm, err := handler.roleRepo.GetPermissions(user) + perm, err := handler.roleRepo.GetPermissions(user, organizationId) //Check to see if the user was created if err == nil { utils.ReturnJson(w, http.StatusOK, perm) } else { - utils.ReturnJsonStatus(w, http.StatusUnsupportedMediaType, false, err.Error()) + utils.ReturnJsonStatus(w, http.StatusServiceUnavailable, false, err.Error()) } - } diff --git a/roles/handlers_test.go b/roles/handlers_test.go new file mode 100644 index 0000000..5dcb1a1 --- /dev/null +++ b/roles/handlers_test.go @@ -0,0 +1,140 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package roles_test + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/reaction-eng/restlib/roles" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/stretchr/testify/assert" + + "github.com/reaction-eng/restlib/users" +) + +func TestHandler_handleUserPermissionsGet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + getUserUser func() users.User + getUserCount int + getUserError error + getPermissionsCount int + getPermissionsResponse []string + getPermissionsError error + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(1023) + return user + }, + getUserCount: 1, + getUserError: nil, + getPermissionsCount: 1, + getPermissionsResponse: []string{"perm1", "perm2"}, + getPermissionsError: nil, + expectedStatus: http.StatusOK, + expectedResponse: "{\"permissions\":[\"perm1\",\"perm2\"]}\n", + }, + { + comment: "not logged in", + getUserUser: func() users.User { + return nil + }, + getUserCount: 0, + getUserError: nil, + getPermissionsCount: 0, + getPermissionsResponse: []string{}, + getPermissionsError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"no_user_logged_in\",\"status\":false}\n", + }, + { + comment: "can't find user with db error", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(1023) + return user + }, + getUserCount: 1, + getUserError: errors.New("can't find user"), + getPermissionsCount: 0, + getPermissionsResponse: []string{"perm1", "perm2"}, + getPermissionsError: nil, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"can't find user\",\"status\":false}\n", + }, + { + comment: "can't find user", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(1023) + return user + }, + getUserCount: 1, + getUserError: users.UserNotFound, + getPermissionsCount: 0, + getPermissionsResponse: []string{"perm1", "perm2"}, + getPermissionsError: nil, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"" + users.UserNotFound.Error() + "\",\"status\":false}\n", + }, + { + comment: "can't find permissions", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(1023) + return user + }, + getUserCount: 1, + getUserError: nil, + getPermissionsCount: 1, + getPermissionsResponse: []string{"perm1", "perm2"}, + getPermissionsError: errors.New("can't get info"), + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"can't get info\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + user := testCase.getUserUser() + mockUserRepo.EXPECT().GetUser(1023).Times(testCase.getUserCount).Return(user, testCase.getUserError) + + permissions := roles.Permissions{ + Permissions: testCase.getPermissionsResponse, + } + + mockRoleRepo := mocks.NewMockRolesRepo(mockCtrl) + mockRoleRepo.EXPECT().GetPermissions(user, 1000).Times(testCase.getPermissionsCount).Return(&permissions, testCase.getPermissionsError) + + handler := roles.NewHandler(mockUserRepo, mockRoleRepo) + router := mocks.NewTestRouterWithUser(handler, user, 1000) + + req := httptest.NewRequest("GET", "http://localhost/users/permissions", nil) + w := httptest.NewRecorder() + + // act + route := router.Handle(w, req) + + // assert + assert.NotNil(t, route) + assert.Equal(t, false, route.Public) + assert.Nil(t, route.ReqPermissions) + assert.Equal(t, testCase.expectedStatus, w.Result().StatusCode) + assert.Equal(t, testCase.expectedResponse, w.Body.String()) + } +} diff --git a/roles/PermissionTable.go b/roles/permissionTable.go similarity index 65% rename from roles/PermissionTable.go rename to roles/permissionTable.go index a25ca78..e7146f4 100644 --- a/roles/PermissionTable.go +++ b/roles/permissionTable.go @@ -3,15 +3,10 @@ package roles -/** -Define an interface for roles -*/ +//go:generate mockgen -destination=../mocks/mock_permissionTable.go -package=mocks github.com/reaction-eng/restlib/roles PermissionTable + type PermissionTable interface { - /** - Get the user with the email. An error is thrown is not found - */ GetPermissions(roleId int) []string - //Look up the role id based upon the name LookUpRoleId(name string) (int, error) } diff --git a/roles/PermissionTableJson.go b/roles/permissionTableJson.go similarity index 58% rename from roles/PermissionTableJson.go rename to roles/permissionTableJson.go index ec67279..9caad6e 100644 --- a/roles/PermissionTableJson.go +++ b/roles/permissionTableJson.go @@ -6,27 +6,20 @@ package roles import ( "encoding/json" "errors" - "log" "os" "strings" ) -//Simple struct to hold the role type Role struct { Name string Permissions []string } -/** -Define a struct for Repo for use with users -*/ type PermissionTableJson struct { - //Hold a map of the strings Roles map[int]Role } -//Provide a method to make a new UserRepoSql -func NewPermissionTableJson(fileName string) *PermissionTableJson { +func NewPermissionTableJson(fileName string) (*PermissionTableJson, error) { //Create a new table permTable := &PermissionTableJson{} @@ -34,34 +27,30 @@ func NewPermissionTableJson(fileName string) *PermissionTableJson { configFileStream, err := os.Open(fileName) defer configFileStream.Close() if err != nil { - log.Fatal(err) + return nil, err } - //Get the json and add to the Params + //Get the json and add to the params jsonParser := json.NewDecoder(configFileStream) err = jsonParser.Decode(&permTable) if err != nil { - log.Fatal(err) + return nil, err } - return permTable + return permTable, nil } -/** -Get the user with the email. An error is thrown is not found -*/ func (repo *PermissionTableJson) GetPermissions(roleId int) []string { //Look up the role - role := repo.Roles[roleId] - - //Else get them - return role.Permissions + role, hasRole := repo.Roles[roleId] + if hasRole { + return role.Permissions + } else { + return []string{} + } } -/** -Get the role id for this name -*/ func (repo *PermissionTableJson) LookUpRoleId(roleLookUp string) (int, error) { //March over each config for index, role := range repo.Roles { @@ -73,15 +62,4 @@ func (repo *PermissionTableJson) LookUpRoleId(roleLookUp string) (int, error) { //It was not found, error an error return -1, errors.New("could not find role " + roleLookUp) - } - -//func RepoDestroyCalc(id int) error { -// for i, t := range usersList { -// if t.Id == id { -// usersList = append(usersList[:i], usersList[i+1:]...) -// return nil -// } -// } -// return fmt.Errorf("Could not find Todo with id of %d to delete", id) -//} diff --git a/roles/permissionTableJson_test.go b/roles/permissionTableJson_test.go new file mode 100644 index 0000000..a46eab2 --- /dev/null +++ b/roles/permissionTableJson_test.go @@ -0,0 +1,285 @@ +package roles_test + +import ( + "errors" + "io/ioutil" + "os" + "testing" + + "github.com/reaction-eng/restlib/roles" + "github.com/stretchr/testify/assert" +) + +func TestNewPermissionTableJson(t *testing.T) { + + testCases := []struct { + jsonString string + expectedError string + isNil bool + expectedRoles map[int]roles.Role + }{ + { + jsonString: `{"roles": { + "1": { + "name": "role 1", + "permissions": [ + "perm1" + ] + } + }}`, + expectedError: "", + isNil: false, + expectedRoles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + }, + }, + { + jsonString: `{"roles": { + "1": { + "name": "role 1", + "permissions": [ + "perm1" + ] + }, + "12": { + "name": "role 2", + "permissions": [ + "perm1","perm3" + ] + } + }}`, + expectedError: "", + isNil: false, + expectedRoles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + 12: { + "role 2", + []string{"perm1", "perm3"}, + }, + }, + }, + { + jsonString: `{"roles": { + "1": { + "name": "role 1", + "permissions": [ + "perm1" + ] + }, + "alpha": { + "name": "role 2", + "permissions": [ + "perm1","perm3" + ] + } + }}`, + expectedError: "json: cannot unmarshal number alpha into Go struct field PermissionTableJson.Roles of type int", + isNil: true, + expectedRoles: map[int]roles.Role{}, + }, + { + jsonString: `{{}`, + expectedError: "invalid character '{' looking for beginning of object key string", + isNil: true, + expectedRoles: map[int]roles.Role{}, + }, + } + + for _, testCase := range testCases { + // arrange + file, err := ioutil.TempFile(os.TempDir(), "testJson*.json") + if err != nil { + assert.Nil(t, err) + } + file.WriteString(testCase.jsonString) + file.Close() + + // act + permTable, err := roles.NewPermissionTableJson(file.Name()) + + // assert + if len(testCase.expectedError) == 0 { + assert.Nil(t, err) + } else { + assert.Equal(t, testCase.expectedError, err.Error()) + } + if testCase.isNil { + assert.Nil(t, permTable) + } else { + assert.NotNil(t, permTable) + assert.NotNil(t, permTable.Roles) + assert.Equal(t, testCase.expectedRoles, permTable.Roles) + } + + // cleanup + os.Remove(file.Name()) + } +} + +func TestPermissionTableJson_GetPermissions(t *testing.T) { + testCases := []struct { + table *roles.PermissionTableJson + roleId int + expectedPermissions []string + }{ + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + }, + }, + roleId: 1, + expectedPermissions: []string{"perm1"}, + }, + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + 12: roles.Role{ + Name: "role 12", + Permissions: []string{"perm3", "perm1"}, + }, + }, + }, + roleId: 12, + expectedPermissions: []string{"perm3", "perm1"}, + }, + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + 12: roles.Role{ + Name: "role 12", + Permissions: []string{"perm3", "perm1"}, + }, + }, + }, + roleId: 1, + expectedPermissions: []string{"perm1"}, + }, + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + 12: roles.Role{ + Name: "role 12", + Permissions: []string{"perm3", "perm1"}, + }, + }, + }, + roleId: 0, + expectedPermissions: []string{}, + }, + } + + for _, testCase := range testCases { + // arrange + // act + permissions := testCase.table.GetPermissions(testCase.roleId) + + // assert + assert.Equal(t, testCase.expectedPermissions, permissions) + } +} + +func TestPermissionTableJson_LookUpRoleId(t *testing.T) { + testCases := []struct { + table *roles.PermissionTableJson + roleLookUp string + expectedRoleId int + expectedError error + }{ + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + }, + }, + roleLookUp: "role 1", + expectedRoleId: 1, + expectedError: nil, + }, + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + 12: roles.Role{ + Name: "role 12", + Permissions: []string{"perm3", "perm1"}, + }, + }, + }, + roleLookUp: "role 12", + expectedRoleId: 12, + expectedError: nil, + }, + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + 12: roles.Role{ + Name: "role 12", + Permissions: []string{"perm3", "perm1"}, + }, + }, + }, + roleLookUp: "role 1", + expectedRoleId: 1, + expectedError: nil, + }, + { + table: &roles.PermissionTableJson{ + Roles: map[int]roles.Role{ + 1: roles.Role{ + Name: "role 1", + Permissions: []string{"perm1"}, + }, + 12: roles.Role{ + Name: "role 12", + Permissions: []string{"perm3", "perm1"}, + }, + }, + }, + roleLookUp: "role 234", + expectedRoleId: -1, + expectedError: errors.New("could not find role role 234"), + }, + } + + for _, testCase := range testCases { + // arrange + // act + id, err := testCase.table.LookUpRoleId(testCase.roleLookUp) + + // assert + assert.Equal(t, testCase.expectedRoleId, id) + assert.Equal(t, testCase.expectedError, err) + } +} diff --git a/roles/Permissions.go b/roles/permissions.go similarity index 96% rename from roles/Permissions.go rename to roles/permissions.go index 4640805..fa69fa0 100644 --- a/roles/Permissions.go +++ b/roles/permissions.go @@ -9,9 +9,6 @@ package roles type Permissions struct { //Store a list of Permissions []string `json:"permissions"` - - //And s list of Roles - } /** @@ -25,7 +22,6 @@ func (perm *Permissions) AllowedTo(tasks ...string) bool { if !contains(perm.Permissions, task) { return false } - } //I Guess we can diff --git a/roles/permissions_test.go b/roles/permissions_test.go new file mode 100644 index 0000000..9bbd84d --- /dev/null +++ b/roles/permissions_test.go @@ -0,0 +1,86 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package roles_test + +import ( + "strings" + "testing" + + "github.com/reaction-eng/restlib/roles" + "github.com/stretchr/testify/assert" +) + +func TestPermissions_AllowedTo(t *testing.T) { + testCases := []struct { + permissions *roles.Permissions + request []string + expectedAllowed bool + }{ + { + permissions: &roles.Permissions{ + Permissions: []string{"perm1", "perm2"}, + }, + request: []string{"perm1"}, + expectedAllowed: true, + }, + { + permissions: &roles.Permissions{ + Permissions: []string{"perm1", "perm2"}, + }, + request: []string{"perm3"}, + expectedAllowed: false, + }, + { + permissions: &roles.Permissions{ + Permissions: []string{"perm1", "perm2"}, + }, + request: []string{"perm1", "perm2"}, + expectedAllowed: true, + }, + { + permissions: &roles.Permissions{ + Permissions: []string{"perm1", "perm2"}, + }, + request: []string{"perm2", "perm1"}, + expectedAllowed: true, + }, + { + permissions: &roles.Permissions{ + Permissions: []string{"perm1", "perm2"}, + }, + request: []string{"perm1", "perm3"}, + expectedAllowed: false, + }, + { + permissions: &roles.Permissions{ + Permissions: []string{"perm1", "perm2"}, + }, + request: []string{}, + expectedAllowed: true, + }, + { + permissions: &roles.Permissions{ + Permissions: []string{}, + }, + request: []string{}, + expectedAllowed: true, + }, + { + permissions: &roles.Permissions{ + Permissions: []string{}, + }, + request: []string{"perm1"}, + expectedAllowed: false, + }, + } + + for _, testCase := range testCases { + // arrange + // act + allowed := testCase.permissions.AllowedTo(testCase.request...) + + // assert + assert.Equal(t, testCase.expectedAllowed, allowed, "permissions: "+strings.Join(testCase.permissions.Permissions, ",")+" request: "+strings.Join(testCase.request, ",")) + } +} diff --git a/roles/repo.go b/roles/repo.go new file mode 100644 index 0000000..9df6f5f --- /dev/null +++ b/roles/repo.go @@ -0,0 +1,16 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package roles + +//go:generate mockgen -destination=../mocks/mock_roles_repo.go -package=mocks -mock_names Repo=MockRolesRepo github.com/reaction-eng/restlib/roles Repo + +import "github.com/reaction-eng/restlib/users" + +type Repo interface { + GetPermissions(user users.User, organizationId int) (*Permissions, error) + + SetRolesByRoleId(user users.User, organizationId int, roles []int) error + + SetRolesByName(user users.User, organizationId int, roles []string) error +} diff --git a/roles/repoSql.go b/roles/repoSql.go new file mode 100644 index 0000000..15b361e --- /dev/null +++ b/roles/repoSql.go @@ -0,0 +1,239 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package roles + +import ( + "database/sql" + "sort" + + "github.com/reaction-eng/restlib/users" +) + +const TableName = "roles" + +type RepoSql struct { + //Hold on to the sql databased + db *sql.DB + + //Store the required statements to reduce compute time + getUserRoles *sql.Stmt + clearUserRoles *sql.Stmt + addUserRole *sql.Stmt + + //We need the role Repo + permTable PermissionTable +} + +func NewRepoMySql(db *sql.DB, roleRepo PermissionTable) (*RepoSql, error) { + + //Define a new repo + newRepo := RepoSql{ + db: db, + permTable: roleRepo, + } + + getRoles, err := db.Prepare("SELECT roleId FROM " + TableName + " WHERE userId = ? AND orgId = ?") + if err != nil { + return nil, err + } + newRepo.getUserRoles = getRoles + + clearRoles, err := db.Prepare("DELETE FROM " + TableName + " WHERE userId = ? AND orgId = ?") + if err != nil { + return nil, err + } + newRepo.clearUserRoles = clearRoles + + addRole, err := db.Prepare("INSERT INTO " + TableName + " (userId,orgId,roleId) VALUES (?, ?, ?)") + //Check for error + if err != nil { + return nil, err + } + newRepo.addUserRole = addRole + + return &newRepo, nil +} + +func NewRepoPostgresSql(db *sql.DB, permTable PermissionTable) (*RepoSql, error) { + + //Define a new repo + newRepo := RepoSql{ + db: db, + permTable: permTable, + } + + getRoles, err := db.Prepare("SELECT roleId FROM " + TableName + " WHERE userId = $1 AND orgId = $2") + if err != nil { + return nil, err + } + newRepo.getUserRoles = getRoles + + clearRoles, err := db.Prepare("DELETE FROM " + TableName + " WHERE userId = $1 AND orgId = $2") + if err != nil { + return nil, err + } + newRepo.clearUserRoles = clearRoles + + addRole, err := db.Prepare("INSERT INTO " + TableName + " (userId,orgId,roleId) VALUES ($1, $2, $3)") + if err != nil { + return nil, err + } + newRepo.addUserRole = addRole + + return &newRepo, nil + +} + +func (repo *RepoSql) GetPermissions(user users.User, organizationId int) (*Permissions, error) { + //Get a list of roles + permissions := make([]string, 0) + + rows, err := repo.getUserRoles.Query(user.Id(), organizationId) + if err != nil { + return nil, err + } + + //Rows is the result of a query. Its cursor starts before the first row of the result set. Use Next to advance through the rows: + defer rows.Close() + for rows.Next() { + //Get the role id + var roleId int + err = rows.Scan(&roleId) + if err != nil { + return nil, err + } + + //Get the permissions + rolePermissions := repo.permTable.GetPermissions(roleId) + + //Push back + permissions = append(permissions, rolePermissions...) + + } + err = rows.Close() + if err != nil { + return nil, err + } + + err = rows.Err() + if err != nil { + return nil, err + } + + //Get the permissions from + return &Permissions{ + Permissions: permissions, + }, nil + +} + +func (repo *RepoSql) GetRoleIds(user users.User, organizationId int) ([]int, error) { + //Get a list of roles + roles := make([]int, 0) + + rows, err := repo.getUserRoles.Query(user.Id(), organizationId) + if err != nil { + return nil, err + } + + //Rows is the result of a query. Its cursor starts before the first row of the result set. Use Next to advance through the rows: + defer rows.Close() + for rows.Next() { + //Get the role id + var roleId int + err = rows.Scan(&roleId) + if err != nil { + return nil, err + } + //Push back + roles = append(roles, roleId) + } + err = rows.Close() + if err != nil { + return nil, err + } + err = rows.Err() + if err != nil { + return nil, err + } + + return roles, nil +} + +func (repo *RepoSql) SetRolesByRoleId(user users.User, organizationId int, roles []int) error { + //Get all of the + currentRoles, err := repo.GetRoleIds(user, organizationId) + userId := user.Id() + + if err != nil { + return err + } + + //If the roles dont' equal replace them + if err != nil || !sameRoles(currentRoles, roles) { + + //Clear all of the roles + _, err := repo.clearUserRoles.Exec(userId, organizationId) + if err != nil { + return err + } + + //Now add each role + for _, roleId := range roles { + _, err = repo.addUserRole.Exec(userId, organizationId, roleId) + if err != nil { + return err + } + } + + return err + } + return nil +} + +func (repo *RepoSql) SetRolesByName(user users.User, organizationId int, roles []string) error { + //Build a list of roles to add + roleIds := make([]int, 0) + + //March over the roles + for _, role := range roles { + //Get the id + roleId, err := repo.permTable.LookUpRoleId(role) + + //Add it to the list + if err == nil { + roleIds = append(roleIds, roleId) + } + } + + return repo.SetRolesByRoleId(user, organizationId, roleIds) +} + +func (repo *RepoSql) CleanUp() { + repo.getUserRoles.Close() + +} + +//See if the roles are the same +func sameRoles(a, b []int) bool { + + // If one is nil, the other must also be nil. + if (a == nil) != (b == nil) { + return false + } + + if len(a) != len(b) { + return false + } + + sort.Ints(a) + sort.Ints(b) + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} diff --git a/roles/repoSql_test.go b/roles/repoSql_test.go new file mode 100644 index 0000000..459a126 --- /dev/null +++ b/roles/repoSql_test.go @@ -0,0 +1,577 @@ +package roles_test + +import ( + "database/sql" + "errors" + "testing" + + "github.com/reaction-eng/restlib/roles" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/stretchr/testify/assert" +) + +func TestNewRepoMySql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("SELECT roleId FROM " + roles.TableName + " WHERE userId = (.+) AND orgId = (.+)") + mock.ExpectPrepare("DELETE FROM " + roles.TableName + " WHERE userId = (.+) AND orgId = (.+)") + mock.ExpectPrepare("INSERT INTO " + roles.TableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTable := mocks.NewMockPermissionTable(mockCtrl) + + // act + repoMySql, err := roles.NewRepoMySql(db, mockTable) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func TestRepoPostgresSql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("SELECT roleId FROM " + roles.TableName + " WHERE userId = (.+) AND orgId = (.+)") + mock.ExpectPrepare("DELETE FROM " + roles.TableName + " WHERE userId = (.+) AND orgId = (.+)") + mock.ExpectPrepare("INSERT INTO " + roles.TableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTable := mocks.NewMockPermissionTable(mockCtrl) + + // act + repoMySql, err := roles.NewRepoPostgresSql(db, mockTable) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func setupSqlMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + mock.ExpectPrepare("SELECT roleId FROM " + roles.TableName + " WHERE userId = (.+) AND orgId = (.+)") + mock.ExpectPrepare("DELETE FROM " + roles.TableName + " WHERE userId = (.+) AND orgId = (.+)") + mock.ExpectPrepare("INSERT INTO " + roles.TableName) + + return db, mock +} + +func TestGetPermissions(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + getUserRolesRows *sqlmock.Rows + getUserRolesError error + setupPermTable func(table *mocks.MockPermissionTable) + expectedPermissions *roles.Permissions + expectedError error + }{ + { + comment: "working", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(10).AddRow(40), + getUserRolesError: nil, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + mockPermTable.EXPECT().GetPermissions(10).Return([]string{"perm1"}) + mockPermTable.EXPECT().GetPermissions(40).Return([]string{"perm2", "perm3"}) + }, + expectedPermissions: &roles.Permissions{ + Permissions: []string{"perm1", "perm2", "perm3"}, + }, + }, + { + comment: "no roles", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}), + getUserRolesError: nil, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + }, + expectedPermissions: &roles.Permissions{ + Permissions: []string{}, + }, + }, + { + comment: "db error", + getUserRolesRows: nil, + getUserRolesError: errors.New("can't access db"), + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + }, + expectedPermissions: nil, + expectedError: errors.New("can't access db"), + }, + { + comment: "scan error", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow("alphea beta"), + getUserRolesError: nil, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + }, + expectedPermissions: nil, + expectedError: errors.New("sql: Scan error on column index 0, name \"rowId\": converting driver.Value type string (\"alphea beta\") to a int: invalid syntax"), + }, + { + comment: "row error", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(10).RowError(0, errors.New("db error")), + getUserRolesError: nil, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + }, + expectedPermissions: nil, + expectedError: errors.New("db error"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + mockTable := mocks.NewMockPermissionTable(mockCtrl) + testCase.setupPermTable(mockTable) + repo, err := roles.NewRepoPostgresSql(db, mockTable) + + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + + dbMock.ExpectQuery("SELECT roleId FROM "+roles.TableName). + WithArgs(34, 1000). + WillReturnRows(testCase.getUserRolesRows). + WillReturnError(testCase.getUserRolesError) + + // act + permissions, err := repo.GetPermissions(user, 1000) + + // assert + assert.Equal(t, testCase.expectedPermissions, permissions) + assert.Equal(t, testCase.expectedError, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestGetRoleIds(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + getUserRolesRows *sqlmock.Rows + getUserRolesError error + expectedRoles []int + expectedError error + }{ + { + comment: "working", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(10).AddRow(40), + getUserRolesError: nil, + expectedRoles: []int{10, 40}, + expectedError: nil, + }, + { + comment: "no roles", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}), + getUserRolesError: nil, + expectedRoles: []int{}, + expectedError: nil, + }, + { + comment: "db error", + getUserRolesRows: nil, + getUserRolesError: errors.New("can't access db"), + expectedRoles: nil, + expectedError: errors.New("can't access db"), + }, + { + comment: "scan error", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow("alphea beta"), + getUserRolesError: nil, + expectedRoles: nil, + expectedError: errors.New("sql: Scan error on column index 0, name \"rowId\": converting driver.Value type string (\"alphea beta\") to a int: invalid syntax"), + }, + { + comment: "row error", + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(10).RowError(0, errors.New("db error")), + getUserRolesError: nil, + expectedRoles: nil, + expectedError: errors.New("db error"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + mockTable := mocks.NewMockPermissionTable(mockCtrl) + repo, err := roles.NewRepoPostgresSql(db, mockTable) + + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + + dbMock.ExpectQuery("SELECT roleId FROM "+roles.TableName). + WithArgs(34, 1000). + WillReturnRows(testCase.getUserRolesRows). + WillReturnError(testCase.getUserRolesError) + + // act + roles, err := repo.GetRoleIds(user, 1000) + + // assert + assert.Equal(t, testCase.expectedRoles, roles) + assert.Equal(t, testCase.expectedError, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestSetRolesByRoleId(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + inputRoles []int + getUserRolesRows *sqlmock.Rows + getUserRolesError error + expectClear bool + clearUserRoles error + setupAddUserRole func(sqlMock *sqlmock.Sqlmock) + expectedError error + }{ + { + comment: "no update", + inputRoles: []int{3, 2, 5}, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(2).AddRow(3).AddRow(5), + getUserRolesError: nil, + expectClear: false, + clearUserRoles: nil, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) {}, + expectedError: nil, + }, + { + comment: "update update", + inputRoles: []int{3, 2, 5, 6}, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(2).AddRow(3).AddRow(5), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 3).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 2).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 5).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 6).WillReturnResult(sqlmock.NewResult(0, 1)) + }, + expectedError: nil, + }, + { + comment: "remove roles update", + inputRoles: []int{}, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(2).AddRow(3).AddRow(5), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + }, + expectedError: nil, + }, + { + comment: "no existing roles update", + inputRoles: []int{3, 2, 5, 6}, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 3).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 2).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 5).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 6).WillReturnResult(sqlmock.NewResult(0, 1)) + }, + expectedError: nil, + }, + { + comment: "get user roles error", + inputRoles: []int{3, 2, 5, 6}, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}), + getUserRolesError: errors.New("get user role error"), + clearUserRoles: nil, + expectClear: false, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + }, + expectedError: errors.New("get user role error"), + }, + { + comment: "can't clear", + inputRoles: []int{3, 2, 5, 6}, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(4), + getUserRolesError: nil, + clearUserRoles: errors.New("can't clear error"), + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + }, + expectedError: errors.New("can't clear error"), + }, + { + comment: "can't add user rol error", + inputRoles: []int{3, 2, 5, 6}, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(4), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 3).WillReturnResult(sqlmock.NewResult(0, 1)).WillReturnError(errors.New("can't add user error")) + }, + expectedError: errors.New("can't add user error"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + mockTable := mocks.NewMockPermissionTable(mockCtrl) + repo, err := roles.NewRepoPostgresSql(db, mockTable) + + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(2).Return(34) + + dbMock.ExpectQuery("SELECT roleId FROM "+roles.TableName). + WithArgs(34, 1000). + WillReturnRows(testCase.getUserRolesRows). + WillReturnError(testCase.getUserRolesError) + + if testCase.expectClear { + dbMock.ExpectExec("DELETE FROM "+roles.TableName). + WithArgs(34, 1000). + WillReturnResult(sqlmock.NewResult(0, 2)). + WillReturnError(testCase.clearUserRoles) + } + + testCase.setupAddUserRole(&dbMock) + + // act + err = repo.SetRolesByRoleId(user, 1000, testCase.inputRoles) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestSetRolesByRoleName(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + setupPermTable func(table *mocks.MockPermissionTable) + inputRoles []string + getUserRolesRows *sqlmock.Rows + getUserRolesError error + expectClear bool + clearUserRoles error + setupAddUserRole func(sqlMock *sqlmock.Sqlmock) + expectedError error + }{ + { + comment: "no update", + inputRoles: []string{"role4", "role 1", "role 2"}, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + mockPermTable.EXPECT().LookUpRoleId("role4").Return(4, nil) + mockPermTable.EXPECT().LookUpRoleId("role 1").Return(1, nil) + mockPermTable.EXPECT().LookUpRoleId("role 2").Return(2, nil) + }, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(1).AddRow(2).AddRow(4), + getUserRolesError: nil, + expectClear: false, + clearUserRoles: nil, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) {}, + expectedError: nil, + }, + { + comment: "update update", + inputRoles: []string{"role4", "role 1", "role 2"}, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + mockPermTable.EXPECT().LookUpRoleId("role4").Return(4, nil) + mockPermTable.EXPECT().LookUpRoleId("role 1").Return(1, nil) + mockPermTable.EXPECT().LookUpRoleId("role 2").Return(2, nil) + }, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(2).AddRow(3).AddRow(5), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 1).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 2).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 4).WillReturnResult(sqlmock.NewResult(0, 1)) + }, + expectedError: nil, + }, + { + comment: "remove roles update", + inputRoles: []string{}, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + }, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(2).AddRow(3).AddRow(5), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + }, + expectedError: nil, + }, + { + comment: "no existing roles update", + inputRoles: []string{"role 3", "role 2", "role 5", "role 6"}, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + mockPermTable.EXPECT().LookUpRoleId("role 3").Return(3, nil) + mockPermTable.EXPECT().LookUpRoleId("role 2").Return(2, nil) + mockPermTable.EXPECT().LookUpRoleId("role 5").Return(5, nil) + mockPermTable.EXPECT().LookUpRoleId("role 6").Return(6, nil) + }, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(5), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 3).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 2).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 5).WillReturnResult(sqlmock.NewResult(0, 1)) + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 6).WillReturnResult(sqlmock.NewResult(0, 1)) + }, + expectedError: nil, + }, + { + comment: "get user roles error", + inputRoles: []string{"role 3", "role 2", "role 5", "role 6"}, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + mockPermTable.EXPECT().LookUpRoleId("role 3").Return(3, nil) + mockPermTable.EXPECT().LookUpRoleId("role 2").Return(2, nil) + mockPermTable.EXPECT().LookUpRoleId("role 5").Return(5, nil) + mockPermTable.EXPECT().LookUpRoleId("role 6").Return(6, nil) + }, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}), + getUserRolesError: errors.New("get user role error"), + clearUserRoles: nil, + expectClear: false, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + }, + expectedError: errors.New("get user role error"), + }, + { + comment: "can't clear", + inputRoles: []string{"role 3", "role 2", "role 5", "role 6"}, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + mockPermTable.EXPECT().LookUpRoleId("role 3").Return(3, nil) + mockPermTable.EXPECT().LookUpRoleId("role 2").Return(2, nil) + mockPermTable.EXPECT().LookUpRoleId("role 5").Return(5, nil) + mockPermTable.EXPECT().LookUpRoleId("role 6").Return(6, nil) + }, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(4), + getUserRolesError: nil, + clearUserRoles: errors.New("can't clear error"), + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + }, + expectedError: errors.New("can't clear error"), + }, + { + comment: "can't add user rol error", + inputRoles: []string{"role 3", "role 2", "role 5", "role 6"}, + setupPermTable: func(mockPermTable *mocks.MockPermissionTable) { + mockPermTable.EXPECT().LookUpRoleId("role 3").Return(3, nil) + mockPermTable.EXPECT().LookUpRoleId("role 2").Return(2, nil) + mockPermTable.EXPECT().LookUpRoleId("role 5").Return(5, nil) + mockPermTable.EXPECT().LookUpRoleId("role 6").Return(6, nil) + }, + getUserRolesRows: sqlmock.NewRows([]string{"rowId"}).AddRow(4), + getUserRolesError: nil, + clearUserRoles: nil, + expectClear: true, + setupAddUserRole: func(sqlMock *sqlmock.Sqlmock) { + (*sqlMock).ExpectExec("INSERT INTO "+roles.TableName).WithArgs(34, 1000, 3).WillReturnResult(sqlmock.NewResult(0, 1)).WillReturnError(errors.New("can't add user error")) + }, + expectedError: errors.New("can't add user error"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + mockTable := mocks.NewMockPermissionTable(mockCtrl) + repo, err := roles.NewRepoPostgresSql(db, mockTable) + + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(2).Return(34) + + testCase.setupPermTable(mockTable) + + dbMock.ExpectQuery("SELECT roleId FROM "+roles.TableName). + WithArgs(34, 1000). + WillReturnRows(testCase.getUserRolesRows). + WillReturnError(testCase.getUserRolesError) + + if testCase.expectClear { + dbMock.ExpectExec("DELETE FROM "+roles.TableName). + WithArgs(34, 1000). + WillReturnResult(sqlmock.NewResult(0, 2)). + WillReturnError(testCase.clearUserRoles) + } + + testCase.setupAddUserRole(&dbMock) + + // act + err = repo.SetRolesByName(user, 1000, testCase.inputRoles) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} diff --git a/routing/Router.go b/routing/MuxRouter.go similarity index 90% rename from routing/Router.go rename to routing/MuxRouter.go index 5626dec..2351727 100644 --- a/routing/Router.go +++ b/routing/MuxRouter.go @@ -15,7 +15,7 @@ import ( /** Create our own router that pulls in the mux router */ -type Router struct { +type MuxRouter struct { // Store the mux router *mux.Router @@ -33,13 +33,13 @@ type RouteProducer interface { GetRoutes() []Route } -//Type def a logger wrapper +//GetType def a logger wrapper type LoggerWrapper func(inner http.Handler, name string) http.Handler /** * Build a new instance of this router. It contains all of the paths so we can ghceck them later */ -func NewRouter(optionsHandler http.HandlerFunc, routes []Route, loggerWrapper LoggerWrapper, routeProducers ...RouteProducer) *Router { +func NewRouter(optionsHandler http.HandlerFunc, routes []Route, loggerWrapper LoggerWrapper, routeProducers ...RouteProducer) *MuxRouter { muxRouter := mux.NewRouter().StrictSlash(true) //Add in an option to handle all options @@ -48,7 +48,7 @@ func NewRouter(optionsHandler http.HandlerFunc, routes []Route, loggerWrapper Lo } //Combine the newrouter into this one - router := Router{ + router := MuxRouter{ muxRouter, make([]Route, 0), } @@ -75,7 +75,7 @@ func NewRouter(optionsHandler http.HandlerFunc, routes []Route, loggerWrapper Lo /** Determines if it is a public path based upon the routes */ -func (router *Router) addRoute(route Route, loggerWrapper LoggerWrapper) { +func (router *MuxRouter) addRoute(route Route, loggerWrapper LoggerWrapper) { // Define a new handler var handler http.Handler = route.HandlerFunc @@ -99,7 +99,7 @@ func (router *Router) addRoute(route Route, loggerWrapper LoggerWrapper) { /** Determines if it is a public path based upon the routes */ -func (router *Router) GetRoute(req *http.Request) *Route { +func (router *MuxRouter) GetRoute(req *http.Request) *Route { //Get the route name from the req if muxRoute := mux.CurrentRoute(req); muxRoute != nil { diff --git a/routing/router.go b/routing/router.go new file mode 100644 index 0000000..1a71c29 --- /dev/null +++ b/routing/router.go @@ -0,0 +1,9 @@ +package routing + +//go:generate mockgen -destination=../mocks/mock_router.go -package=mocks github.com/reaction-eng/restlib/routing Router + +import "net/http" + +type Router interface { + GetRoute(req *http.Request) *Route +} diff --git a/static/Handlers.go b/static/Handlers.go index c145912..15a7828 100644 --- a/static/Handlers.go +++ b/static/Handlers.go @@ -4,9 +4,9 @@ package static import ( + "github.com/gorilla/mux" "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/utils" - "github.com/gorilla/mux" "html/template" "net/http" ) diff --git a/static/Repo.go b/static/Repo.go index 77e0aaf..fd45ce4 100644 --- a/static/Repo.go +++ b/static/Repo.go @@ -3,9 +3,6 @@ package static -/** -Define an interface that all Calc Repos must follow -*/ type Repo interface { /** Get the public static diff --git a/static/RepoCache.go b/static/RepoCache.go index f208181..ce95c3d 100644 --- a/static/RepoCache.go +++ b/static/RepoCache.go @@ -6,32 +6,22 @@ package static import ( "github.com/reaction-eng/restlib/cache" "github.com/reaction-eng/restlib/configuration" - "github.com/reaction-eng/restlib/google" + "github.com/reaction-eng/restlib/file" ) -/** -Define a struct for RepoMem for news -*/ type RepoCache struct { //Store the cache - cas cache.ObjectCache + cas cache.Cache - //We also need googl - drive *google.Drive + drive file.Storage //Store the public and private - privateConfig *configuration.Configuration - publicConfig *configuration.Configuration + privateConfig configuration.Configuration + publicConfig configuration.Configuration } -//Provide a method to make a new AnimalRepoSql -func NewRepoCache(drive *google.Drive, cas cache.ObjectCache, privateConfigFile string, publicConfigFile string) *RepoCache { +func NewRepoCache(drive file.Storage, cas cache.Cache, privateConfig configuration.Configuration, publicConfig configuration.Configuration) *RepoCache { - //Create a new config - privateConfig, _ := configuration.NewConfiguration(privateConfigFile) - publicConfig, _ := configuration.NewConfiguration(publicConfigFile) - - //Define a new repo newRepo := RepoCache{ cas: cas, drive: drive, @@ -80,7 +70,6 @@ func (repo *RepoCache) GetStaticPublicDocument(path string) (string, error) { Get the public static */ func (repo *RepoCache) GetStaticPrivateDocument(path string) (string, error) { - //Look up the document id from the config documentId, err := repo.privateConfig.GetStringError(path) diff --git a/stl/Creator.go b/stl/Creator.go index d54eda0..6899d2e 100644 --- a/stl/Creator.go +++ b/stl/Creator.go @@ -177,9 +177,9 @@ func ExtrudeTriAndCreateMesh(pts []Vertex, normExtrude *Vertex, ExtrudLen float6 var ThirStretch Vertex // - OneStretch =OneCap.trans(normExtrude,ExtrudLen) - TwoStretch =TwoCap.trans(normExtrude,ExtrudLen) - ThirStretch=ThirCap.trans(normExtrude,ExtrudLen) + OneStretch = OneCap.trans(normExtrude, ExtrudLen) + TwoStretch = TwoCap.trans(normExtrude, ExtrudLen) + ThirStretch = ThirCap.trans(normExtrude, ExtrudLen) // //OneStretch[0] = OneCap[0]+ normExtrude[0]*ExtrudLen //OneStretch[1] = OneCap[1]+ normExtrude[1]*ExtrudLen @@ -211,14 +211,14 @@ func ExtrudeTriAndCreateMesh(pts []Vertex, normExtrude *Vertex, ExtrudLen float6 ), ) - for lineIndex:=0; lineIndex< len(pts)-1;lineIndex++{ - OneFace :=Extrude(pts[lineIndex], pts[lineIndex+1] , normExtrude, ExtrudLen) - left:= NewElement( + for lineIndex := 0; lineIndex < len(pts)-1; lineIndex++ { + OneFace := Extrude(pts[lineIndex], pts[lineIndex+1], normExtrude, ExtrudLen) + left := NewElement( OneFace.end, OneFace.start, OneFace.startStrech, ) - right:= NewElement( + right := NewElement( OneFace.end, OneFace.startStrech, OneFace.endStrecth, @@ -228,21 +228,20 @@ func ExtrudeTriAndCreateMesh(pts []Vertex, normExtrude *Vertex, ExtrudLen float6 } //Add the face based from endpoint to staring point - OneFace :=Extrude(pts[len(pts)-1], pts[0] , normExtrude, ExtrudLen) - leftEndLoop:= NewElement( + OneFace := Extrude(pts[len(pts)-1], pts[0], normExtrude, ExtrudLen) + leftEndLoop := NewElement( OneFace.end, OneFace.start, OneFace.startStrech, ) - rightEndLoop:= NewElement( + rightEndLoop := NewElement( OneFace.end, OneFace.startStrech, OneFace.endStrecth, ) elements = append(elements, *leftEndLoop, *rightEndLoop) - - // Add the parallel top hat + // Add the parallel top hat elements = append(elements, *NewElement( OneStretch, @@ -258,63 +257,63 @@ func ExtrudeTriAndCreateMesh(pts []Vertex, normExtrude *Vertex, ExtrudLen float6 } -func (facemesh *Mesh)ExtrudeFaceAndCreateMesh(normExtrude *Vertex, ExtrudLen float64) (*Mesh, error) { +func (facemesh *Mesh) ExtrudeFaceAndCreateMesh(normExtrude *Vertex, ExtrudLen float64) (*Mesh, error) { //We need at least three pts if len(facemesh.Elements) < 1 { return nil, errors.New(" NOT Empty face for generating mesh") } - bottomFace := make([]Element,0) + bottomFace := make([]Element, 0) faceEdge := make([]TriLine, 0) - for _,ele := range facemesh.Elements{ + for _, ele := range facemesh.Elements { // creat the corresponding triLine object to store the corresponding edge of triangular - faceEdge =append(faceEdge, *ele.GetEdge()) + faceEdge = append(faceEdge, *ele.GetEdge()) - bottomFace = append(bottomFace, *ele.Translation( normExtrude, ExtrudLen) ) + bottomFace = append(bottomFace, *ele.Translation(normExtrude, ExtrudLen)) // translation the top face to bottom face, also reverse direciton - }// the index of faceEdge is same as the element of face mesh + } // the index of faceEdge is same as the element of face mesh // storing the edge of face - boundEdge := make([]Edge,0) - for ind, TriangEle := range faceEdge { + boundEdge := make([]Edge, 0) + for ind, TriangEle := range faceEdge { - for indSec := ind+1; indSec 0 { + user := testCase.newUserUser() + mockHelper.EXPECT().NewEmptyUser().Times(testCase.addUserCount).Return(user) + mockHelper.EXPECT().AddUser(user).Times(testCase.addUserCount).Return(user, testCase.addUserError) + } + + mockHelper.EXPECT().TokenGenerator().Times(testCase.tokenGeneratorCount).Return(token) + mockHelper.EXPECT().IssueOneTimePasswordRequest(token, 34, "user@example.com").Times(testCase.issueOneTimePasswordRequestCount).Return(testCase.issueOneTimePasswordRequestError) + mockHelper.EXPECT().AddUserToOrganization(gomock.Any(), 43).Times(testCase.addUserToOrgCount).Return(nil) + + handler := users.NewOneTimePasswordHandler(mockHelper) + router := mocks.NewTestRouter(handler) // not logged in + + req := httptest.NewRequest("GET", testCase.url, nil) + w := httptest.NewRecorder() + + // act + route := router.Handle(w, req) + + // assert + assert.NotNil(t, route) + assert.Equal(t, true, route.Public) + assert.Nil(t, route.ReqPermissions) + assert.Equal(t, testCase.expectedStatus, w.Result().StatusCode) + assert.Equal(t, testCase.expectedResponse, w.Body.String()) + } +} + +func TestHandler_handleOneTimePasswordLoginPut(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + body io.Reader + getUserByEmailUser func() users.User + getUserByEmailCount int + getUserByEmailError error + checkForOneTimePasswordTokenCount int + checkForOneTimePasswordTokenError error + activateUserCount int + activateUserError error + getUserCount int + getUserError error + createJwtTokenCount int + useTokenCount int + useTokenError error + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + body: strings.NewReader(`{"email":"user@example.com", "login_token":"login_token", "organizationId": 45 }`), + getUserByEmailUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(2).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + user.EXPECT().Activated().Times(1).Return(true) + user.EXPECT().Organizations().Times(1).Return([]int{45}) + user.EXPECT().SetToken("jwtToken").Times(1) + + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForOneTimePasswordTokenCount: 1, + checkForOneTimePasswordTokenError: nil, + activateUserCount: 0, + activateUserError: nil, + getUserCount: 0, + getUserError: nil, + createJwtTokenCount: 1, + useTokenCount: 1, + useTokenError: nil, + expectedStatus: http.StatusCreated, + expectedResponse: "{}\n", + }, + { + comment: "mal formed json", + body: strings.NewReader(`{{}`), + getUserByEmailUser: func() users.User { + return nil + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + checkForOneTimePasswordTokenCount: 0, + checkForOneTimePasswordTokenError: nil, + activateUserCount: 0, + activateUserError: nil, + getUserCount: 0, + getUserError: nil, + createJwtTokenCount: 0, + useTokenCount: 0, + useTokenError: nil, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"invalid character '{' looking for beginning of object key string\",\"status\":false}\n", + }, + { + comment: "can't find user", + body: strings.NewReader(`{"email":"user@example.com", "login_token":"login_token", "organizationId": 45 }`), + getUserByEmailUser: func() users.User { + return nil + }, + getUserByEmailCount: 1, + getUserByEmailError: users.UserNotFound, + checkForOneTimePasswordTokenCount: 0, + checkForOneTimePasswordTokenError: nil, + activateUserCount: 0, + activateUserError: nil, + getUserCount: 0, + getUserError: nil, + createJwtTokenCount: 0, + useTokenCount: 0, + useTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"onetimepassword_forbidden\",\"status\":false}\n", + }, + { + comment: "user not in org", + body: strings.NewReader(`{"email":"user@example.com", "login_token":"login_token", "organizationId": 45 }`), + getUserByEmailUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Organizations().Times(1).Return([]int{1043}) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForOneTimePasswordTokenCount: 0, + checkForOneTimePasswordTokenError: nil, + activateUserCount: 0, + activateUserError: nil, + getUserCount: 0, + getUserError: nil, + createJwtTokenCount: 0, + useTokenCount: 0, + useTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"user_not_in_organization\",\"status\":false}\n", + }, + { + comment: "invalid token ", + body: strings.NewReader(`{"email":"user@example.com", "login_token":"login_token", "organizationId": 45 }`), + getUserByEmailUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Organizations().Times(1).Return([]int{45}) + + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForOneTimePasswordTokenCount: 1, + checkForOneTimePasswordTokenError: errors.New("wrong token"), + activateUserCount: 0, + activateUserError: nil, + getUserCount: 0, + getUserError: nil, + createJwtTokenCount: 0, + useTokenCount: 0, + useTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"onetimepassword_forbidden\",\"status\":false}\n", + }, + { + comment: "active users when need ", + body: strings.NewReader(`{"email":"user@example.com", "login_token":"login_token", "organizationId": 45 }`), + getUserByEmailUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(3).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + user.EXPECT().Activated().Times(1).Return(false) + user.EXPECT().Organizations().Times(1).Return([]int{45}) + user.EXPECT().SetToken("jwtToken").Times(1) + + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForOneTimePasswordTokenCount: 1, + checkForOneTimePasswordTokenError: nil, + activateUserCount: 1, + activateUserError: nil, + getUserCount: 1, + getUserError: nil, + createJwtTokenCount: 1, + useTokenCount: 1, + useTokenError: nil, + expectedStatus: http.StatusCreated, + expectedResponse: "{}\n", + }, + { + comment: "activate users when need returns if can't activate", + body: strings.NewReader(`{"email":"user@example.com", "login_token":"login_token", "organizationId": 45 }`), + getUserByEmailUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Activated().Times(1).Return(false) + user.EXPECT().Organizations().Times(1).Return([]int{45}) + + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForOneTimePasswordTokenCount: 1, + checkForOneTimePasswordTokenError: nil, + activateUserCount: 1, + activateUserError: errors.New("can't activate"), + getUserCount: 0, + getUserError: nil, + createJwtTokenCount: 0, + useTokenCount: 1, + useTokenError: nil, + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"onetimepassword_forbidden\",\"status\":false}\n", + }, + { + comment: "activate users when need returns if can't get user", + body: strings.NewReader(`{"email":"user@example.com", "login_token":"login_token", "organizationId": 45 }`), + getUserByEmailUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(2).Return(34) + user.EXPECT().Activated().Times(1).Return(false) + user.EXPECT().Organizations().Times(1).Return([]int{45}) + + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForOneTimePasswordTokenCount: 1, + checkForOneTimePasswordTokenError: nil, + activateUserCount: 1, + activateUserError: nil, + getUserCount: 1, + getUserError: errors.New("can't get user"), + createJwtTokenCount: 0, + useTokenCount: 1, + useTokenError: nil, + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"onetimepassword_forbidden\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + user := testCase.getUserByEmailUser() + mockHelper.EXPECT().GetUserByEmail("user@example.com").Times(testCase.getUserByEmailCount).Return(user, testCase.getUserByEmailError) + mockHelper.EXPECT().CheckForOneTimePasswordToken(34, "login_token").Times(testCase.checkForOneTimePasswordTokenCount).Return(103243, testCase.checkForOneTimePasswordTokenError) + mockHelper.EXPECT().UseToken(103243).Times(testCase.useTokenCount).Return(testCase.useTokenError) + + mockHelper.EXPECT().ActivateUser(user).Times(testCase.activateUserCount).Return(testCase.activateUserError) + mockHelper.EXPECT().GetUser(34).Times(testCase.getUserCount).Return(user, testCase.getUserError) + + mockHelper.EXPECT().CreateJWTToken(34, 45, "user@example.com").Times(testCase.createJwtTokenCount).Return("jwtToken") + + handler := users.NewOneTimePasswordHandler(mockHelper) + router := mocks.NewTestRouter(handler) // not logged in + + req := httptest.NewRequest("POST", "http://localhost/users/onetimelogin", testCase.body) + w := httptest.NewRecorder() + + // act + route := router.Handle(w, req) + + // assert + assert.NotNil(t, route) + assert.Equal(t, true, route.Public) + assert.Nil(t, route.ReqPermissions) + assert.Equal(t, testCase.expectedStatus, w.Result().StatusCode) + assert.Equal(t, testCase.expectedResponse, w.Body.String()) + } +} diff --git a/users/Repo.go b/users/repo.go similarity index 59% rename from users/Repo.go rename to users/repo.go index 7d303c5..ae899e3 100644 --- a/users/Repo.go +++ b/users/repo.go @@ -3,9 +3,13 @@ package users -/** -Define an interface that all Calc Repos must follow -*/ +import "errors" + +//go:generate mockgen -destination=../mocks/mock_users_repo.go -package=mocks -mock_names Repo=MockUserRepo github.com/reaction-eng/restlib/users Repo + +var UserNotFound = errors.New("user_not_found") +var UserNotInOrganization = errors.New("user_not_in_organization") + type Repo interface { /** Get the user with the email. An error is thrown is not found @@ -32,19 +36,16 @@ type Repo interface { */ ActivateUser(user User) error - /** - Allow databases to be closed - */ - CleanUp() - /** Create empty user */ NewEmptyUser() User + AddUserToOrganization(user User, orgId int) error + RemoveUserFromOrganization(user User, orgId int) error + /** List all users */ - ListAllUsers() ([]int, error) - ListAllActiveUsers() ([]int, error) + ListUsers(onlyActive bool, organizations []int) ([]int, error) } diff --git a/users/RepoSql.go b/users/repoSql.go similarity index 51% rename from users/RepoSql.go rename to users/repoSql.go index 22e3851..ec4ef90 100644 --- a/users/RepoSql.go +++ b/users/repoSql.go @@ -6,12 +6,15 @@ package users import ( "database/sql" "errors" - "github.com/reaction-eng/restlib/utils" - "log" "strings" "time" + + "github.com/reaction-eng/restlib/utils" ) +const UserTableName = "users" +const UserOrgTableName = "userOrganizations" + /** Define a struct for Repo for use with users */ @@ -22,162 +25,151 @@ type RepoSql struct { //Also store the table name tableName string - //Store the required statements to reduce comput time - addUserStatement *sql.Stmt - getUserStatement *sql.Stmt - getUserByEmailStatement *sql.Stmt - updateUserStatement *sql.Stmt - activateStatement *sql.Stmt - listAllUsersStatement *sql.Stmt - - //Store the nullable time object - + //Store the required statements to reduce compute time + addUserStatement *sql.Stmt + getUserStatement *sql.Stmt + getUserByEmailStatement *sql.Stmt + updateUserStatement *sql.Stmt + activateStatement *sql.Stmt + listAllUsersStatement *sql.Stmt + getUserOrganizations *sql.Stmt + addUserToOrganization *sql.Stmt + removeUserFromOrganization *sql.Stmt } //Provide a method to make a new UserRepoSql -func NewRepoMySql(db *sql.DB, tableName string) *RepoSql { +func NewRepoMySql(db *sql.DB) (*RepoSql, error) { //Define a new repo newRepo := RepoSql{ - db: db, - tableName: tableName, - } - - //Create the table if it is not already there - //Create a table - _, err := db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(id int NOT NULL AUTO_INCREMENT, email TEXT, password TEXT, activation Date, PRIMARY KEY (id) )") - if err != nil { - log.Fatal(err) + db: db, } - //Add calc data to table - addUser, err := db.Prepare("INSERT INTO " + tableName + "(email,password) VALUES (?, ?)") + addUser, err := db.Prepare("INSERT INTO " + UserTableName + "(email,password) VALUES (?, ?)") //Check for error if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.addUserStatement = addUser - //get user statement - getUser, err := db.Prepare("SELECT * FROM " + tableName + " where id = ?") + getUser, err := db.Prepare("SELECT * FROM " + UserTableName + " where id = ?") //Check for error if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.getUserStatement = getUser - //get calc statement - getUserByEmail, err := db.Prepare("SELECT * FROM " + tableName + " where email like ?") + getUserByEmail, err := db.Prepare("SELECT * FROM " + UserTableName + " where email like ?") //Check for error if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.getUserByEmailStatement = getUserByEmail - //update the user - updateStatement, err := db.Prepare("UPDATE " + tableName + " SET email = ?, password = ? WHERE id = ?") - - //Check for error + updateStatement, err := db.Prepare("UPDATE " + UserTableName + " SET email = ?, password = ? WHERE id = ?") if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.updateUserStatement = updateStatement - //Activate User statemetn - activateStatement, err := db.Prepare("UPDATE " + tableName + " SET activation = ? WHERE id = ?") + activateStatement, err := db.Prepare("UPDATE " + UserTableName + " SET activation = ? WHERE id = ?") if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.activateStatement = activateStatement - //update the user - listAllUsers, err := db.Prepare("SELECT id, activation FROM " + tableName) + listAllUsers, err := db.Prepare("SELECT id, activation FROM " + UserTableName) if err != nil { - log.Fatal(err) + return nil, err } newRepo.listAllUsersStatement = listAllUsers + getUserOrganizations, err := db.Prepare("SELECT orgId FROM " + UserOrgTableName + " where userId = ?") + if err != nil { + return nil, err + } + newRepo.getUserOrganizations = getUserOrganizations + + addUserToOrganization, err := db.Prepare("INSERT INTO " + UserOrgTableName + " (userId,orgId,joinDate) VALUES (?, ?, ?)") + if err != nil { + return nil, err + } + newRepo.addUserToOrganization = addUserToOrganization + + removeUserFromOrganization, err := db.Prepare("DELETE FROM " + UserOrgTableName + " WHERE userId = ? AND orgId = ?") + if err != nil { + return nil, err + } + newRepo.removeUserFromOrganization = removeUserFromOrganization + //Return a point - return &newRepo + return &newRepo, nil } -//Provide a method to make a new UserRepoSql -func NewRepoPostgresSql(db *sql.DB, tableName string) *RepoSql { +func NewRepoPostgresSql(db *sql.DB) (*RepoSql, error) { //Define a new repo newRepo := RepoSql{ - db: db, - tableName: tableName, - } - - //Create the table if it is not already there - //Create a table - _, err := db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + "(id SERIAL PRIMARY KEY, email TEXT NOT NULL, password TEXT NOT NULL, activation Date)") - if err != nil { - log.Fatal(err) + db: db, } - //Add calc data to table - addUser, err := db.Prepare("INSERT INTO " + tableName + "(email,password) VALUES ($1, $2)") + addUser, err := db.Prepare("INSERT INTO " + UserTableName + "(email,password) VALUES ($1, $2)") //Check for error if err != nil { - log.Fatal(err) + return nil, err } - ////Store it newRepo.addUserStatement = addUser - //get calc statement - getUser, err := db.Prepare("SELECT * FROM " + tableName + " where id = $1") - //Check for error + getUser, err := db.Prepare("SELECT * FROM " + UserTableName + " where id = $1") if err != nil { - log.Fatal(err) + return nil, err } - ////Store it newRepo.getUserStatement = getUser - //get calc statement - getUserByEmail, err := db.Prepare("SELECT * FROM " + tableName + " where email like $1") - //Check for error + getUserByEmail, err := db.Prepare("SELECT * FROM " + UserTableName + " where email like $1") if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.getUserByEmailStatement = getUserByEmail - //update the user - updateStatement, err := db.Prepare("UPDATE " + tableName + " SET email = $1, password = $2 WHERE id = $3") - - //Check for error + updateStatement, err := db.Prepare("UPDATE " + UserTableName + " SET email = $1, password = $2 WHERE id = $3") if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.updateUserStatement = updateStatement - //Activate User statemetn - activateStatement, err := db.Prepare("UPDATE " + tableName + " SET activation = $1 WHERE id = $2") + activateStatement, err := db.Prepare("UPDATE " + UserTableName + " SET activation = $1 WHERE id = $2") if err != nil { - log.Fatal(err) + return nil, err } - //Store it newRepo.activateStatement = activateStatement - //update the user - listAllUsers, err := db.Prepare("SELECT id, activation FROM " + tableName) + listAllUsers, err := db.Prepare("SELECT id, activation FROM " + UserTableName) if err != nil { - log.Fatal(err) + return nil, err } newRepo.listAllUsersStatement = listAllUsers - //Return a point - return &newRepo + getUserOrganizations, err := db.Prepare("SELECT orgId FROM " + UserOrgTableName + " where userId = $1") + if err != nil { + return nil, err + } + newRepo.getUserOrganizations = getUserOrganizations + addUserToOrganization, err := db.Prepare("INSERT INTO " + UserOrgTableName + " (userId,orgId,joinDate) VALUES ($1, $2, $3)") + if err != nil { + return nil, err + } + newRepo.addUserToOrganization = addUserToOrganization + + removeUserFromOrganization, err := db.Prepare("DELETE FROM " + UserOrgTableName + " WHERE userId = $1 AND orgId = $2") + if err != nil { + return nil, err + } + newRepo.removeUserFromOrganization = removeUserFromOrganization + + return &newRepo, nil } /** @@ -198,7 +190,8 @@ func (repo *RepoSql) GetUserByEmail(email string) (User, error) { //Use a useful error if err == sql.ErrNoRows { - err = errors.New("login_email_not_found") + return nil, UserNotFound + } else if err != nil { return nil, err } @@ -206,7 +199,12 @@ func (repo *RepoSql) GetUserByEmail(email string) (User, error) { user.activated_ = activationDate.Valid user.passwordlogin_ = len(user.password_) > 0 - //Return the user calcs + orgIds, err := repo.listUserOrganizations(user.Id_) + if err != nil { + return nil, err + } + user.SetOrganizations(orgIds...) + return &user, err } @@ -214,7 +212,6 @@ func (repo *RepoSql) GetUserByEmail(email string) (User, error) { Look up the user by id and return if they were found */ func (repo *RepoSql) GetUser(id int) (User, error) { - //var dataResult string var user BasicUser //Store the sql time @@ -225,60 +222,35 @@ func (repo *RepoSql) GetUser(id int) (User, error) { //Use a useful error if err == sql.ErrNoRows { - err = errors.New("login_user_id_not_found") + return nil, UserNotFound + } else if err != nil { + return nil, err } //Store if this is activated user.activated_ = activationDate.Valid user.passwordlogin_ = len(user.password_) > 0 - //Return the user calcs - return &user, err -} - -/** -List all of the users -*/ -func (repo *RepoSql) ListAllUsers() ([]int, error) { - //Put in the list - list := make([]int, 0) - - //Get the value //id int NOT NULL AUTO_INCREMENT, email TEXT, password TEXT, PRIMARY KEY (id) - rows, err := repo.listAllUsersStatement.Query() + orgIds, err := repo.listUserOrganizations(user.Id_) if err != nil { - log.Fatal(err) - } - defer rows.Close() - for rows.Next() { - var id int - //Store the sql time - var activationDate utils.NullTime - - err := rows.Scan(&id, &activationDate) - if err != nil { - return nil, err - } - - //Append the row - list = append(list, id) - + return nil, err } - err = rows.Err() + user.SetOrganizations(orgIds...) - return list, err + return &user, err } /** List all of the users */ -func (repo *RepoSql) ListAllActiveUsers() ([]int, error) { +func (repo *RepoSql) ListUsers(onlyActive bool, organizations []int) ([]int, error) { //Put in the list list := make([]int, 0) //Get the value //id int NOT NULL AUTO_INCREMENT, email TEXT, password TEXT, PRIMARY KEY (id) rows, err := repo.listAllUsersStatement.Query() if err != nil { - log.Fatal(err) + return nil, err } defer rows.Close() for rows.Next() { @@ -292,7 +264,7 @@ func (repo *RepoSql) ListAllActiveUsers() ([]int, error) { } //Append the row - if activationDate.Valid { + if !onlyActive || activationDate.Valid { list = append(list, id) } } @@ -301,18 +273,23 @@ func (repo *RepoSql) ListAllActiveUsers() ([]int, error) { return list, err } -/** -Add the user to the database -*/ func (repo *RepoSql) AddUser(newUser User) (User, error) { + _, userFoundError := repo.GetUserByEmail(newUser.Email()) + if userFoundError == nil { + return nil, errors.New("user_email_in_user") + } + if userFoundError != UserNotFound { + return nil, userFoundError + } + //Add the info //execute the statement//(userId,name,input,flow) _, err := repo.addUserStatement.Exec(newUser.Email(), newUser.Password()) //Check for error if err != nil { - return newUser, err + return nil, err } //Now look up the person by email @@ -320,26 +297,13 @@ func (repo *RepoSql) AddUser(newUser User) (User, error) { } -/** -Update the user table. No checks are made here, -*/ func (repo *RepoSql) UpdateUser(user User) (User, error) { - //Update the user statement - //Just update the info //execute the statement//"UPDATE " + tableName + " SET email = ?, password = ? WHERE id = ?" _, err := repo.updateUserStatement.Exec(user.Email(), user.Password(), user.Id()) - //Check for error - if err != nil { - log.Fatal(err) - } - return user, err } -/** -Update the user table. No checks are made here, -*/ func (repo *RepoSql) ActivateUser(user User) error { //Get the current time actTime := utils.NullTime{ @@ -349,18 +313,56 @@ func (repo *RepoSql) ActivateUser(user User) error { //Just update the info//"UPDATE " + tableName + " SET activation = $1 WHERE id = $2") _, err := repo.activateStatement.Exec(actTime, user.Id()) + return err +} - //Check for error +func (repo *RepoSql) AddUserToOrganization(user User, orgId int) error { + _, err := repo.addUserToOrganization.Exec(user.Id(), orgId, time.Now()) + return err +} + +func (repo *RepoSql) RemoveUserFromOrganization(user User, orgId int) error { + result, err := repo.removeUserFromOrganization.Exec(user.Id(), orgId) + if err != nil { + return err + } + + rowsChange, err := result.RowsAffected() if err != nil { - log.Fatal(err) + return err + } + if rowsChange < 1 { + return errors.New("no_organizations_removed") } return err } -/** -Clean up the database, nothing much to do -*/ +func (repo *RepoSql) listUserOrganizations(userId int) ([]int, error) { + //Put in the list + list := make([]int, 0) + + rows, err := repo.getUserOrganizations.Query(userId) + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + var orgId int + + err := rows.Scan(&orgId) + if err != nil { + return nil, err + } + + list = append(list, orgId) + } + err = rows.Err() + + return list, err + +} + func (repo *RepoSql) CleanUp() { repo.addUserStatement.Close() repo.getUserByEmailStatement.Close() @@ -369,19 +371,6 @@ func (repo *RepoSql) CleanUp() { repo.listAllUsersStatement.Close() } -/** -Clean up the database, nothing much to do -*/ func (repo *RepoSql) NewEmptyUser() User { return &BasicUser{} } - -//func RepoDestroyCalc(id int) error { -// for i, t := range usersList { -// if t.Id == id { -// usersList = append(usersList[:i], usersList[i+1:]...) -// return nil -// } -// } -// return fmt.Errorf("Could not find Todo with id of %d to delete", id) -//} diff --git a/users/repoSql_test.go b/users/repoSql_test.go new file mode 100644 index 0000000..bb4c363 --- /dev/null +++ b/users/repoSql_test.go @@ -0,0 +1,1216 @@ +// Copyright 2019 Reaction Engineering International. All rights reserved. +// Use of this source code is governed by the MIT license in the file LICENSE.txt. + +package users_test + +import ( + "database/sql" + "errors" + "testing" + "time" + + "github.com/reaction-eng/restlib/mocks" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/users" + "github.com/stretchr/testify/assert" +) + +func TestNewRepoMySql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("INSERT INTO " + users.UserTableName) + mock.ExpectPrepare("SELECT \\* FROM " + users.UserTableName) + mock.ExpectPrepare("SELECT \\* FROM " + users.UserTableName) + mock.ExpectPrepare("UPDATE " + users.UserTableName) + mock.ExpectPrepare("UPDATE " + users.UserTableName) + mock.ExpectPrepare("SELECT id, activation FROM " + users.UserTableName) + mock.ExpectPrepare("SELECT orgId FROM " + users.UserOrgTableName) + mock.ExpectPrepare("INSERT INTO " + users.UserOrgTableName) + mock.ExpectPrepare("DELETE FROM " + users.UserOrgTableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + // act + repoMySql, err := users.NewRepoMySql(db) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func TestNewRepoPostgresSql(t *testing.T) { + // arrange + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectPrepare("INSERT INTO " + users.UserTableName) + mock.ExpectPrepare("SELECT \\* FROM " + users.UserTableName) + mock.ExpectPrepare("SELECT \\* FROM " + users.UserTableName) + mock.ExpectPrepare("UPDATE " + users.UserTableName) + mock.ExpectPrepare("UPDATE " + users.UserTableName) + mock.ExpectPrepare("SELECT id, activation FROM " + users.UserTableName) + mock.ExpectPrepare("SELECT orgId FROM " + users.UserOrgTableName) + mock.ExpectPrepare("INSERT INTO " + users.UserOrgTableName) + mock.ExpectPrepare("DELETE FROM " + users.UserOrgTableName) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + // act + repoMySql, err := users.NewRepoPostgresSql(db) + + // assert + assert.Nil(t, err) + assert.NotNil(t, repoMySql) +} + +func setupSqlMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + mock.ExpectPrepare("INSERT INTO " + users.UserTableName) + mock.ExpectPrepare("SELECT \\* FROM " + users.UserTableName) + mock.ExpectPrepare("SELECT \\* FROM " + users.UserTableName) + mock.ExpectPrepare("UPDATE " + users.UserTableName) + mock.ExpectPrepare("UPDATE " + users.UserTableName) + mock.ExpectPrepare("SELECT id, activation FROM " + users.UserTableName) + mock.ExpectPrepare("SELECT orgId FROM " + users.UserOrgTableName) + mock.ExpectPrepare("INSERT INTO " + users.UserOrgTableName) + mock.ExpectPrepare("DELETE FROM " + users.UserOrgTableName) + + return db, mock +} + +func TestResetRepoSql_GetUserByEmail(t *testing.T) { + testCases := []struct { + comment string + emailInput string + expectedError error + query func(*sqlmock.ExpectedQuery) + queryOrgs func(*sqlmock.ExpectedQuery) + userId int + userOrgs []int + userEmail string + userToken string + userActivated bool + userPasswordLogin bool + }{ + { + comment: "working", + emailInput: "user@example.com", + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: true, + }, + { + comment: "crazy email input", + emailInput: " usEr@example.Com ", + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: true, + }, + { + comment: "no rows", + emailInput: "user@example.com", + expectedError: users.UserNotFound, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com").WillReturnError(sql.ErrNoRows) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + }, + { + comment: "nil date should be non activated", + emailInput: "user@example.com", + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", nil)) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: false, + userPasswordLogin: true, + }, + { + comment: "empty password should prevent user from logging in", + emailInput: "user@example.com", + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: false, + }, + { + comment: "empty password and nil date should trigger bools", + emailInput: "user@example.com", + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "", nil)) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: false, + userPasswordLogin: false, + }, + { + comment: "get org error should return nil", + emailInput: "user@example.com", + expectedError: errors.New("could not get org info"), + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)). + WillReturnError(errors.New("could not get org info")) + }, + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + repo, err := users.NewRepoPostgresSql(db) + testCase.query(dbMock.ExpectQuery("SELECT \\* FROM users")) + testCase.queryOrgs(dbMock.ExpectQuery("SELECT orgId FROM userOrganizations")) + assert.Nil(t, err) + + // act + user, err := repo.GetUserByEmail(testCase.emailInput) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err == nil { + assert.Equal(t, testCase.userId, user.Id()) + assert.Equal(t, testCase.userOrgs, user.Organizations()) + assert.Equal(t, testCase.userEmail, user.Email()) + assert.Equal(t, testCase.userActivated, user.Activated()) + assert.Equal(t, testCase.userPasswordLogin, user.PasswordLogin()) + assert.Equal(t, testCase.userToken, user.Token()) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_GetUser(t *testing.T) { + testCases := []struct { + comment string + idInput int + expectedError error + query func(*sqlmock.ExpectedQuery) + queryOrgs func(*sqlmock.ExpectedQuery) + userId int + userOrgs []int + userEmail string + userToken string + userActivated bool + userPasswordLogin bool + }{ + { + comment: "working", + idInput: 43, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: true, + }, + { + comment: "other db error", + idInput: 43, + expectedError: errors.New("other db error"), + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", time.Now())). + WillReturnError(errors.New("other db error")) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: true, + }, + { + comment: "no rows", + idInput: 43, + expectedError: users.UserNotFound, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43).WillReturnError(sql.ErrNoRows) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + }, + { + comment: "nil date should be non activated", + idInput: 43, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", nil)) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23).AddRow(32)) + }, + userId: 43, + userOrgs: []int{54, 23, 32}, + userEmail: "user@example.com", + userToken: "", + userActivated: false, + userPasswordLogin: true, + }, + { + comment: "empty password should prevent user from logging in", + idInput: 43, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: false, + }, + { + comment: "empty password and nil date should trigger bools", + idInput: 43, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "", nil)) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"}). + AddRow(54).AddRow(23)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: false, + userPasswordLogin: false, + }, + { + comment: "no orgs should work", + idInput: 43, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"})) + }, + userId: 43, + userOrgs: []int{}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: true, + }, + { + comment: "org db error", + idInput: 43, + expectedError: errors.New("org db error"), + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.com", "password", time.Now())) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"})). + WillReturnError(errors.New("org db error")) + }, + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.query(dbMock.ExpectQuery("SELECT \\* FROM users")) + testCase.queryOrgs(dbMock.ExpectQuery("SELECT orgId FROM userOrganizations")) + + // act + user, err := repo.GetUser(testCase.idInput) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err == nil { + assert.Equal(t, testCase.userId, user.Id()) + assert.Equal(t, testCase.userOrgs, user.Organizations()) + assert.Equal(t, testCase.userEmail, user.Email()) + assert.Equal(t, testCase.userActivated, user.Activated()) + assert.Equal(t, testCase.userPasswordLogin, user.PasswordLogin()) + assert.Equal(t, testCase.userToken, user.Token()) + } + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_ListUsers(t *testing.T) { + testCases := []struct { + comment string + onlyActive bool + orgList []int //TODO: add org test + expectedUsers []int + expectedError error + query func(query *sqlmock.ExpectedQuery) + }{ + { + comment: "working", + onlyActive: false, + orgList: nil, + expectedUsers: []int{32, 52, 2432, 23}, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WillReturnRows( + sqlmock.NewRows( + []string{"id", "activationDate"}). + AddRow(32, time.Now()). + AddRow(52, time.Now()). + AddRow(2432, time.Now()). + AddRow(23, time.Now())) + }, + }, + { + comment: "db broken", + onlyActive: false, + orgList: nil, + expectedUsers: nil, + expectedError: errors.New("db broken"), + query: func(query *sqlmock.ExpectedQuery) { + query. + WillReturnError(errors.New("db broken")) + }, + }, + { + comment: "row errors out", + onlyActive: false, + orgList: nil, + expectedUsers: []int{32, 52}, + expectedError: errors.New("row error"), + query: func(query *sqlmock.ExpectedQuery) { + query. + WillReturnRows( + sqlmock.NewRows( + []string{"id", "activationDate"}). + AddRow(32, time.Now()). + AddRow(52, time.Now()).RowError(2, errors.New("row error")). + AddRow(2432, time.Now()). + AddRow(23, time.Now())) + }, + }, + { + comment: "empty list", + onlyActive: false, + orgList: nil, + expectedUsers: []int{}, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WillReturnRows( + sqlmock.NewRows( + []string{"id", "activationDate"})) + }, + }, + { + comment: "with both activated and non active", + onlyActive: false, + orgList: nil, + expectedUsers: []int{32, 52, 2432, 23}, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WillReturnRows( + sqlmock.NewRows( + []string{"id", "activationDate"}). + AddRow(32, time.Now()). + AddRow(52, nil). + AddRow(2432, nil). + AddRow(23, time.Now())) + }, + }, + { + comment: "only active", + onlyActive: true, + orgList: nil, + expectedUsers: []int{32, 23}, + expectedError: nil, + query: func(query *sqlmock.ExpectedQuery) { + query. + WillReturnRows( + sqlmock.NewRows( + []string{"id", "activationDate"}). + AddRow(32, time.Now()). + AddRow(52, nil). + AddRow(2432, nil). + AddRow(23, time.Now())) + }, + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.query(dbMock.ExpectQuery("SELECT id, activation FROM users")) + + // act + userList, err := repo.ListUsers(testCase.onlyActive, testCase.orgList) + + // assert + assert.Equal(t, testCase.expectedError, err) + assert.Equal(t, testCase.expectedUsers, userList) + + // cleanup + db.Close() + mockCtrl.Finish() + } +} + +func TestResetRepoSql_AddUser(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + expectedError error + inputUser func() users.User + checkUserStatementExec func(exec *sqlmock.ExpectedQuery) + checkUserQueryOrgs func(*sqlmock.ExpectedQuery) + addUserStatementExec func(exec *sqlmock.ExpectedExec) + getUserByEmailQuery func(query *sqlmock.ExpectedQuery) + queryOrgs func(*sqlmock.ExpectedQuery) + userId int + userEmail string + userToken string + userActivated bool + userPasswordLogin bool + }{ + { + comment: "working", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Return("user@example.info").Times(3) + user.EXPECT().Password().Return("hashed password").Times(1) + return user + }, + expectedError: nil, + checkUserStatementExec: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnError(sql.ErrNoRows) + }, + addUserStatementExec: func(exec *sqlmock.ExpectedExec) { + exec. + WithArgs("user@example.info", "hashed password"). + WillReturnResult(sqlmock.NewResult(3, 3)) + }, + getUserByEmailQuery: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.info", "password", nil)) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"})) + }, + userId: 43, + userEmail: "user@example.info", + userToken: "", + userActivated: false, + userPasswordLogin: true, + }, + { + comment: "user already exists", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Return("user@example.info").Times(1) + user.EXPECT().Password().Return("hashed password").Times(0) + return user + }, + expectedError: errors.New("user_email_in_user"), + checkUserStatementExec: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.info", "password", nil)) + }, + checkUserQueryOrgs: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43). + WillReturnRows( + sqlmock.NewRows( + []string{"orgId"})) + }, + addUserStatementExec: func(exec *sqlmock.ExpectedExec) { + + }, + getUserByEmailQuery: func(query *sqlmock.ExpectedQuery) { + + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + }, + }, + { + comment: "add user statement error", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Return("user@example.info").Times(2) + user.EXPECT().Password().Return("hashed password").Times(1) + return user + }, + expectedError: errors.New("db error"), + checkUserStatementExec: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.info", "password", nil)). + WillReturnError(sql.ErrNoRows) + }, + addUserStatementExec: func(exec *sqlmock.ExpectedExec) { + exec. + WithArgs("user@example.info", "hashed password"). + WillReturnResult(sqlmock.NewResult(3, 3)). + WillReturnError(errors.New("db error")) + }, + getUserByEmailQuery: func(query *sqlmock.ExpectedQuery) { + + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + }, + }, + { + comment: "get user by email error", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Return("user@example.info").Times(3) + user.EXPECT().Password().Return("hashed password").Times(1) + return user + }, + expectedError: errors.New("db error"), + checkUserStatementExec: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnError(sql.ErrNoRows) + }, + addUserStatementExec: func(exec *sqlmock.ExpectedExec) { + exec. + WithArgs("user@example.info", "hashed password"). + WillReturnResult(sqlmock.NewResult(3, 3)) + }, + getUserByEmailQuery: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.info", "password", nil)). + WillReturnError(errors.New("db error")) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + + }, + }, + { + comment: "get user by email error", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Return("user@example.info").Times(3) + user.EXPECT().Password().Return("hashed password").Times(1) + return user + }, + expectedError: errors.New("db error"), + checkUserStatementExec: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnError(sql.ErrNoRows) + }, + addUserStatementExec: func(exec *sqlmock.ExpectedExec) { + exec. + WithArgs("user@example.info", "hashed password"). + WillReturnResult(sqlmock.NewResult(3, 3)) + }, + getUserByEmailQuery: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnRows( + sqlmock.NewRows( + []string{"id", "email", "password", "activationDate"}). + AddRow(43, "user@example.info", "password", nil)). + WillReturnError(errors.New("db error")) + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + + }, + }, + { + comment: "get user by email error in check", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Return("user@example.info").Times(1) + user.EXPECT().Password().Return("hashed password").Times(0) + return user + }, + expectedError: errors.New("db error"), + checkUserStatementExec: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.info"). + WillReturnError(errors.New("db error")) + }, + addUserStatementExec: func(exec *sqlmock.ExpectedExec) { + }, + getUserByEmailQuery: func(query *sqlmock.ExpectedQuery) { + }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + + }, + }, + } + + for _, testCase := range testCases { + // arrange + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.checkUserStatementExec(dbMock.ExpectQuery("SELECT \\* FROM users ")) + if testCase.checkUserQueryOrgs != nil { + testCase.checkUserQueryOrgs(dbMock.ExpectQuery("SELECT orgId FROM userOrganizations")) + } + testCase.addUserStatementExec(dbMock.ExpectExec("INSERT INTO users ")) + testCase.getUserByEmailQuery(dbMock.ExpectQuery("SELECT \\* FROM users ")) + testCase.queryOrgs(dbMock.ExpectQuery("SELECT orgId FROM userOrganizations")) + + // act + user, err := repo.AddUser(testCase.inputUser()) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err == nil { + assert.Equal(t, testCase.userId, user.Id()) + assert.Equal(t, testCase.userEmail, user.Email()) + assert.Equal(t, testCase.userActivated, user.Activated()) + assert.Equal(t, testCase.userPasswordLogin, user.PasswordLogin()) + assert.Equal(t, testCase.userToken, user.Token()) + } + + // cleanup + db.Close() + } +} + +func TestResetRepoSql_UpdateUser(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + inputUser func() users.User + updateUserExec func(exec *sqlmock.ExpectedExec) + expectedError error + }{ + { + comment: "working", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + user.EXPECT().Email().Return("user@example.info").Times(1) + user.EXPECT().Password().Return("hashed password").Times(1) + return user + }, + updateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs("user@example.info", "hashed password", 34). + WillReturnResult(sqlmock.NewResult(3, 3)) + }, + expectedError: nil, + }, + { + comment: "update user error", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + user.EXPECT().Email().Return("user@example.info").Times(1) + user.EXPECT().Password().Return("hashed password").Times(1) + return user + }, + updateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs("user@example.info", "hashed password", 34). + WillReturnResult(sqlmock.NewResult(3, 3)). + WillReturnError(errors.New("db error")) + + }, + expectedError: errors.New("db error"), + }, + } + + for _, testCase := range testCases { + // arrange + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.updateUserExec(dbMock.ExpectExec("UPDATE user ")) + + userInput := testCase.inputUser() + + // act + user, err := repo.UpdateUser(userInput) + + // assert + assert.Equal(t, testCase.expectedError, err) + assert.Equal(t, userInput, user) + + // cleanup + db.Close() + } +} + +func TestResetRepoSql_ActivateUser(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + inputUser func() users.User + activateUserExec func(exec *sqlmock.ExpectedExec) + expectedError error + }{ + { + comment: "working", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(sqlmock.AnyArg(), 34). + WillReturnResult(sqlmock.NewResult(3, 3)) + }, + expectedError: nil, + }, + { + comment: "activate with error", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(sqlmock.AnyArg(), 34). + WillReturnResult(sqlmock.NewResult(3, 3)). + WillReturnError(errors.New("db error")) + + }, + expectedError: errors.New("db error"), + }, + } + + for _, testCase := range testCases { + // arrange + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.activateUserExec(dbMock.ExpectExec("UPDATE user ")) + + userInput := testCase.inputUser() + + // act + err := repo.ActivateUser(userInput) + + // assert + assert.Equal(t, testCase.expectedError, err) + + // cleanup + db.Close() + } +} + +func TestResetRepoSql_AddUserToOrganization(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + inputUser func() users.User + orgId int + activateUserExec func(exec *sqlmock.ExpectedExec) + expectedError error + }{ + { + comment: "working", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(34, 3234, sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(3, 3)) + }, + orgId: 3234, + expectedError: nil, + }, + { + comment: "add with error", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(34, 3234, sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(3, 3)). + WillReturnError(errors.New("db error")) + + }, + orgId: 3234, + expectedError: errors.New("db error"), + }, + } + + for _, testCase := range testCases { + // arrange + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.activateUserExec(dbMock.ExpectExec("INSERT INTO userOrganizations ")) + + userInput := testCase.inputUser() + + // act + err := repo.AddUserToOrganization(userInput, testCase.orgId) + + // assert + assert.Equal(t, testCase.expectedError, err) + + // cleanup + db.Close() + } +} + +func TestResetRepoSql_RemoveUserFromOrganization(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + inputUser func() users.User + orgId int + activateUserExec func(exec *sqlmock.ExpectedExec) + expectedError error + }{ + { + comment: "working", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(34, 3234). + WillReturnResult(sqlmock.NewResult(3, 3)) + }, + orgId: 3234, + expectedError: nil, + }, + { + comment: "add with error", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(34, 3234). + WillReturnResult(sqlmock.NewResult(3, 3)). + WillReturnError(errors.New("db error")) + + }, + orgId: 3234, + expectedError: errors.New("db error"), + }, + { + comment: "no rows errors", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(34, 3234). + WillReturnResult(sqlmock.NewResult(3, 0)) + }, + orgId: 3234, + expectedError: errors.New("no_organizations_removed"), + }, { + comment: "errors from results", + inputUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(34).Times(1) + return user + }, + activateUserExec: func(query *sqlmock.ExpectedExec) { + query. + WithArgs(34, 3234). + WillReturnResult(sqlmock.NewErrorResult(errors.New("errors from results"))) + }, + orgId: 3234, + expectedError: errors.New("errors from results"), + }, + } + + for _, testCase := range testCases { + // arrange + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.activateUserExec(dbMock.ExpectExec("DELETE FROM userOrganizations ")) + + userInput := testCase.inputUser() + + // act + err := repo.RemoveUserFromOrganization(userInput, testCase.orgId) + + // assert + assert.Equal(t, testCase.expectedError, err) + + // cleanup + db.Close() + } +} diff --git a/utils/Base64File.go b/utils/Base64File.go index 5491969..1092d53 100644 --- a/utils/Base64File.go +++ b/utils/Base64File.go @@ -22,9 +22,10 @@ type Base64File struct { mime string } -/** -Decode the base 64 string -*/ +func NewBase64FileFromData(name string, data []byte) *Base64File { + return &Base64File{name: name, data: data} +} + func NewBase64FileFromForm(file multipart.File, fileInfo *multipart.FileHeader) (*Base64File, error) { //Store the file name and mime // Read entire JPG into byte slice. @@ -35,11 +36,10 @@ func NewBase64FileFromForm(file multipart.File, fileInfo *multipart.FileHeader) b64File := Base64File{ data: content, name: fileInfo.Filename, - mime: fileInfo.Header.Get("Content-Type"), + mime: fileInfo.Header.Get("Content-GetType"), } return &b64File, error - } /** diff --git a/utils/context.go b/utils/context.go new file mode 100644 index 0000000..405f1b6 --- /dev/null +++ b/utils/context.go @@ -0,0 +1,23 @@ +package utils + +import ( + "context" + "errors" +) + +const UserKey = "user" +const OrganizationKey = "organization" + +var UserNotLoggedInError = errors.New("no_user_logged_in") + +func UserFromContext(context context.Context) (userId int, organizationId int, err error) { + //We have gone through the auth, so we should know the id of the logged in user + loggedInUserString := context.Value(UserKey) + organizationIdString := context.Value(OrganizationKey) + + if loggedInUserString == nil || organizationIdString == nil { + return 0, 0, UserNotLoggedInError + } + + return loggedInUserString.(int), organizationIdString.(int), nil +} diff --git a/utils/json.go b/utils/json.go index e21695e..38dc75b 100644 --- a/utils/json.go +++ b/utils/json.go @@ -44,7 +44,7 @@ Provide a support method to return json func ReturnJson(w http.ResponseWriter, statusCode int, data interface{}) { //Assume it is always json - w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.Header().Set("Content-GetType", "application/json; charset=UTF-8") //Pass in the code w.WriteHeader(statusCode) // unprocessable entity @@ -62,7 +62,7 @@ Provide a support method to return json func ReturnJsonNoEscape(w http.ResponseWriter, statusCode int, data interface{}) { //Assume it is always json - w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.Header().Set("Content-GetType", "application/json; charset=UTF-8") //Pass in the code w.WriteHeader(statusCode) // unprocessable entity